O pacote dplyr

O dplyr é o pacote mais útil para realizar transformação de dados, aliando simplicidade e eficiência de uma forma elegante. Os scripts em R que fazem uso inteligente dos verbos dplyr e as facilidades do operador pipe tendem a ficar mais legíveis e organizados sem perder velocidade de execução.

As principais funções do dplyr são:

  • select() - seleciona colunas
  • arrange() - ordena a base
  • filter() - filtra linhas
  • mutate() - cria/modifica colunas
  • group_by() - agrupa a base
  • summarise() - sumariza a base

Todas essas funções seguem as mesmas características:

  • O input é sempre uma tibble e o output é sempre um tibble.
  • Colocamos a tibble no primeiro argumento e o que queremos fazer nos outros argumentos.
  • A utilização é facilitada com o emprego do operador %>%.

As principais vantagens de se usar o dplyr em detrimento das funções do R base são:

  • Manipular dados se torna uma tarefa muito mais simples.
  • O código fica mais intuitivo de ser escrito e mais simples de ser lido.
  • O pacote dplyr utiliza C e C++ por trás da maioria das funções, o que geralmente torna o código mais rápido.
  • É possível trabalhar com diferentes fontes de dados, como bases relacionais (SQL) e data.table.

Se você ainda não tiver o dplyr instalado, rode o código abaixo.

# install.packages("dplyr")
library(dplyr)

Neste capítulo, vamos trabalhar com uma base de filmes do IMDB.

Assim, utilizaremos o objeto imdb para acessar os dados.

Agora, vamos avaliar com mais detalhes as principais funções do pacote dplyr.

Selecionando colunas

Para selecionar colunas, utilizamos a função select().

O primeiro argumento da função é a base de dados e os demais argumentos são os nomes das colunas que você gostaria de selecionar. Repare que você não precisa colocar o nome da coluna entre aspas.

select(imdb, titulo)

Você também pode selecionar várias colunas.

select(imdb, titulo, ano, orcamento)

O operador : é muito útil para selecionar colunas consecutivas.

select(imdb, titulo:cor)

O dplyr possui o conjunto de funções auxiliares muito úteis para seleção de colunas. As principais são:

  • starts_with(): para colunas que começam com um texto padrão
  • ends_with(): para colunas que terminam com um texto padrão
  • contains(): para colunas que contêm um texto padrão

Selecionamos a seguir todas as colunas que começam com o texto “ator”.

select(imdb, starts_with("ator"))

Para retirar colunas da base, base acrescentar um - antes da seleção.

imdb %>%
  select(-ano, - diretor)
imdb %>%
  select(-starts_with("ator"))

Exercícios

Utilize a base imdb nos exercícios a seguir.

1. Teste aplicar a função glimpse() do pacote {dplyr} à base imdb. O que ela faz?

2. Crie uma tabela com apenas as colunas titulo, diretor, e orcamento. Salve em um objeto chamado imdb_simples.

3. Selecione apenas as colunas ator_1, ator_2 e ator_3 usando o ajudante contains().

4. Usando a função select() (e seus ajudantes), escreva códigos que retornem a base IMDB sem as colunas ator_1, ator_2 e ator_3. Escreva todas as soluções diferentes que você conseguir pensar.

Ordenando a base

Para ordenar linhas, utilizamos a função arrange(). O primeiro argumento é a base de dados. Os demais argumentos são as colunas pelas quais queremos ordenar as linhas. No exemplo a seguir, ordenamos as linhas da base por ordem crescente de orçamento.

arrange(imdb, orcamento)

Também podemos ordenar de forma decrescente usando a função desc().

arrange(imdb, desc(orcamento))

E claro, ordenar segundo duas ou mais colunas.

arrange(imdb, desc(ano), desc(orcamento))

Exercícios

Utilize a base imdb nos exercícios a seguir.

1. Ordene os filmes em ordem crescente de ano e decrescente de receita e salve em um objeto chamado filmes_ordenados.

2. Selecione apenas as colunas titulo e orcamento e então ordene de forma decrescente pelo orcamento.

O pipe em ação

Na grande maioria dos casos, vamos aplicar mais de uma função de manipulação em uma base para obtermos a tabela que desejamos. Poderíamos, por exemplo, querer uma tabela apenas com o título e ano dos filmes, ordenada de forma crescente de lançamento. Para fazer isso, poderíamos aninhar as funções

arrange(select(imdb, titulo, ano), ano)

ou criar um objeto intermediário

tab_titulo_ano <- select(imdb, titulo, ano)
arrange(tab_titulo_ano, ano)

Os dois códigos funcionam e levam ao mesmo resultado, mas não são muito boas.

A primeira alternativa é ruim de escrever, já que precisamos escrever primeiro a função que roda por último, e de ler, pois é difícil identificar qual argumento pertence a qual função.

A segunda alternativa é ruim pois exige a criação de objetos auxiliares. Se quiséssimos aplicar 10 operações na base, precisaríamos criar 9 objetos intermediários.

A solução para aplicar diversas operações de manipulação em uma base de dados é aplicar o operador pipe: %>%.

imdb %>% 
  select(titulo, ano) %>% 
  arrange(ano)

O que está sendo feito no código com pipe? Da primeira para a segunda linha, estamos aplicando a função select() à base imdb. Da segunda para a terceira, estamos aplicando a função arrange() à base resultante da função select().

O resultado desse código é identico às tentativas sem pipe, com a vantagem de termos escrito o código na ordem em que as funções são aplicadas, de termos um código muito mais legível e de não precisarmos utilizar objetos intermediários.

Filtrando linhas

Para filtrar valores de uma coluna da base, utilizamos a função filter().

imdb %>% filter(nota_imdb > 9)

Podemos selecionar apenas as colunas título e nota para visualizarmos as notas:

imdb %>% 
  filter(nota_imdb > 9) %>% 
  select(titulo, nota_imdb)

Podemos estender o filtro para duas ou mais colunas. Para isso, separamos cada operação por uma vírgula.

imdb %>% filter(ano > 2010, nota_imdb > 8.5)

Também podemos fazer operações com as colunas da base dentro da função filter. O código abaixo devolve uma tabela apenas com os filmes que lucraram.

imdb %>% filter(receita - orcamento > 0)

Naturalmente, podemos filtrar colunas categóricas. O exemplo abaixo retorna uma tabela apenas com os filmes com a Angelina Jolie Pitt ou o Brad Pitt no papel principal.

imdb %>%
  filter(ator_1 %in% c('Angelina Jolie Pitt', "Brad Pitt"))

Para filtrar textos sem correspondência exata, podemos utilizar a função auxiliar str_detect() do pacote {stringr}. Ela serve para verificar se cada string de um vetor contém um determinado padrão de texto.

library(stringr)
str_detect(
  string = c("a", "aa","abc", "bc", "A", NA), 
  pattern = "a"
)
## [1]  TRUE  TRUE  TRUE FALSE FALSE    NA

Podemos utilizá-la para filtrar apenas os filmes que contêm o gênero ação.

# A coluna gêneros apresenta todos os gêneros dos filmes concatenados
imdb$generos[1:6]
## [1] "Action|Adventure|Fantasy|Sci-Fi"                          
## [2] "Action|Adventure|Fantasy"                                 
## [3] "Action|Thriller"                                          
## [4] "Action|Adventure|Sci-Fi"                                  
## [5] "Action|Adventure|Romance"                                 
## [6] "Adventure|Animation|Comedy|Family|Fantasy|Musical|Romance"
# Podemos detectar se o gênero Action aparece na string
str_detect(
  string = imdb$generos[1:6],
  pattern = "Action"
)
## [1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE
# Aplicamos essa lógica dentro da função filter, para a coluna completa
imdb %>% filter(str_detect(generos, "Action"))

Exercícios

Utilize a base imdb nos exercícios a seguir.

1. Crie um objeto chamado filmes_pb apenas com filmes preto e branco.

2. Crie um objeto chamado curtos_legais com filmes de 90 minutos ou menos de duração e nota no imdb maior do que 8.5.

3. Retorne tabelas (tibbles) apenas com:

  • a. filmes coloridos anteriores a 1950;

  • b. filmes do “Woody Allen” ou do “Wes Anderson”;

  • c. filmes do “Steven Spielberg” ordenados de forma decrescente por ano, mostrando apenas as colunas titulo e ano;

  • d. filmes que tenham “Action” ou “Comedy” entre os seus gêneros;

  • e. filmes que tenham “Action” e “Comedy” entre os seus gêneros e tenha nota_imdb maior que 8;

  • f. filmes que não possuem informação tanto de receita quanto de orçamento (isto é, possuem NA em ambas as colunas).

Modificando e criando novas colunas

Para modificar uma coluna existente ou criar uma nova coluna, utilizamos a função mutate(). O código abaixo divide os valores da coluna duração por 60, mudando a unidade de medida dessa variável de minutos para horas.

imdb %>% mutate(duracao = duracao/60)

Também poderíamos ter criado essa variável em uma nova coluna. Repare que a nova coluna duracao_horas é colocada no final da tabela.

imdb %>% mutate(duracao_horas = duracao/60)

Podemos fazer qualquer operação com uma ou mais colunas. A única regra é que o resultado da operação retorne um vetor com comprimento igual ao número de linhas da base (ou com comprimento 1 para distribuir um mesmo valor em todas as linhas). Você também pode criar/modificar quantas colunas quiser dentro de um mesmo mutate.

imdb %>% 
  mutate(lucro = receita - orcamento, pais = "Estados Unidos") %>% 
  select(titulo, lucro, pais)

Exercícios

Utilize a base imdb nos exercícios a seguir.

1. Crie uma coluna chamada prejuizo (orcamento - receita) e salve a nova tabela em um objeto chamado imdb_prejuizo. Em seguida, filtre apenas os filmes que deram prejuízo e ordene a tabela por ordem crescente de prejuízo.

2. Fazendo apenas uma chamada da função mutate(), crie as seguintes colunas novas na base imdb:

  • a. lucro = receita - orcamento

  • b. lucro_medio

  • c. lucro_relativo = (lucro - lucro_medio)/lucro_medio

  • d. houve_lucro = ifelse(lucro > 0, "sim", "não")

3. Crie uma nova coluna que classifique o filme em "recente" (posterior a 2000) e "antigo" (de 2000 para trás).

Summarisando a base

Sumarização é a técnica de se resumir um conjunto de dados utilizando alguma métrica de interesse. A média, a mediana, a variância, a frequência, a proporção, por exemplo, são tipos de sumarização que trazem diferentes informações sobre uma variável.

Para sumarizar uma coluna da base, utilizamos a função summarize(). O código abaixo resume a coluna orçamento pela sua média.

imdb %>% summarize(media_orcamento = mean(orcamento, na.rm = TRUE))

Repare que a saída da função continua sendo uma tibble.

Podemos calcular diversas sumarizações diferentes em um mesmo summarize. Cada sumarização será uma coluna da nova base.

imdb %>% summarise(
  media_orcamento = mean(orcamento, na.rm = TRUE),
  mediana_orcamento = median(orcamento, na.rm = TRUE),
  variancia_orcamento = var(orcamento, na.rm = TRUE)
)

E também sumarizar diversas colunas.

imdb %>% summarize(
  media_orcamento = mean(orcamento, na.rm = TRUE),
  media_receita = mean(receita, na.rm = TRUE),
  media_lucro = mean(receita - orcamento, na.rm = TRUE)
)

Muitas vezes queremos sumarizar uma coluna agrupada pelas categorias de uma segunda coluna. Para isso, além do summarize, utilizamos também a função group_by().

O código a seguir calcula a receita média dos filmes para cada categoria da coluna “cor”.

imdb %>% 
  group_by(cor) %>% 
  summarise(receita_media = mean(receita, na.rm = TRUE))

A única alteração que a função group_by() faz na base é a marcação de que a base está agrupada.

imdb %>% group_by(cor)

Exercícios

Utilize a base imdb nos exercícios a seguir.

1. Calcule a duração média e mediana dos filmes da base.

2. Calcule o lucro médio dos filmes com duração menor que 60 minutos.

3. Apresente na mesma tabela o lucro médio dos filmes com duracao menor que 60 minutos e o lucro médio dos filmes com duracao maior ou igual a 60 minutos.

4. Retorne tabelas (tibbles) apenas com:

  • a. a nota IMDB média dos filmes por tipo de classificacao;

  • b. a receita média e mediana dos filmes por ano;

  • c. apenas o nome dos diretores com mais de 10 filmes.

Juntando duas bases

Podemos juntar duas tabelas a partir de uma (coluna) chave utilizando a função left_join(). Como exempo, vamos inicialmente calcular o lucro médio dos filmes de cada diretor e salvar no objeto tab_lucro_diretor.

tab_lucro_diretor <- imdb %>%
  group_by(diretor) %>% 
  summarise(lucro_medio = mean(receita - orcamento, na.rm = TRUE))
tab_lucro_diretor

E se quisermos colocar essa informação na base original? Basta usar a função left_join() utilizando a coluna diretor como chave. Observe que a coluna lucro_medio aparece agora no fim da tabela.

imdb_com_lucro_medio <- left_join(imdb, tab_lucro_diretor, by = "diretor")
imdb_com_lucro_medio

Na tabela imdb_com_lucro_medio, como na tabela imdb, cada linha continua a representar um filme diferente, mas agora temos também a informação do lucro médio do diretor de cada filme.

A primeira linha, por exemplo, traz as informações do filme Avatar. O valor do lucro_medio nessa linha representa o lucro médio de todos os filmes do James Cameron, que é o diretor de Avatar. Com essa informação, podemos calcular o quanto o lucro do Avatar se afasta do lucro médio do James Cameron.

imdb_com_lucro_medio %>% 
  mutate(
    lucro = receita - orcamento,
    lucro_relativo = (lucro - lucro_medio)/lucro_medio,
    lucro_relativo = scales::percent(lucro_relativo)
  ) %>% 
  select(titulo, diretor, lucro, lucro_medio, lucro_relativo)

Observamos então que o Avatar obteve um lucro aproximadamente 169% maior que a média dos filmes do James Cameron.

Além da função left_join(), também são muito utilizadas as funções right_join() e full_join().

  • right_join(): retorna todas as linhas da base y e todas as colunas das bases x e y. Linhas de y sem correspondentes em x receberão NA na nova base.

  • full_join(): retorna todas as linhas e colunas de xe y. Valores sem correspondência entre as bases receberão NA na nova base.

A figura a seguir esquematiza as operações dessas funções:

Exercícios

1. Utilize a base imdb para resolver os itens a seguir.

a. Salve em um novo objeto uma tabela com a nota média dos filmes de cada diretor. Essa tabela deve conter duas colunas (diretor e nota_imdb_media) e cada linha deve ser um diretor diferente.

b. Use o left_join() para trazer a coluna nota_imdb_media da tabela do item anterior para a tabela imdb original.

dplyr 1.0

A versão 1.0 do pacote dplyr foi oficialmente lançada em junho de 2020 e contou com diversas novidades. Vamos falar das principais mudanças:

  • A nova função across(), que facilita aplicar uma mesma operação em várias colunas.

  • A repaginada função rowwise(), para fazer operações por linha.

  • Novas funcionalidades das funções select() e rename() e a nova função relocate().

Para trabalhar essas funções, vamos utilizar a base casas do pacote dados. Para instalar esse pacote, rode os códigos abaixo:

# install.packages("remotes")
# remotes::install_github("cienciadedatos/dados")

Para trazer os dados para o nosso environment, podemos rodar:

casas <- dados::casas

A base casas possui dados de venda de casas na cidade de Ames, nos Estados Unidos. São 2930 linhas e 77 colunas, sendo que cada linha corresponde a uma casa vendida e cada coluna a uma característica da casa ou da venda. Essa versão é uma tradução da base original, que pode ser encontrada no pacote AmesHousing:

install.packages("AmesHousing")
data(ames_raw, package = "AmesHousing")

A função across()

A função across() substitui a família de verbos _all(), _if e _at(). A ideia é facilitar a aplicação de uma operação a diversas colunas da base. Para sumarizar a base para mais de uma variável, antigamente poderíamos fazer:

casas %>%
  group_by(geral_qualidade) %>%
  summarise(
    lote_area_media = mean(lote_area, na.rm = TRUE),
    venda_valor_medio = mean(venda_valor, na.rm = TRUE)
  )

Ou então utilizar a função summarise_at():

casas %>%
  group_by(geral_qualidade) %>%
  summarise_at(
    .vars = vars(lote_area, venda_valor),
    list(mean),
    na.rm = TRUE
  )

Com a nova função across(), fazemos:

casas %>%
  group_by(geral_qualidade) %>%
  summarise(across(
    .cols = c(lote_area, venda_valor),
    .fns = mean, 
    na.rm = TRUE
  ))

A sintaxe é parecida com a função summarise_at(), mas agora não precisamos mais usar a função vars() e nem usar list(nome_da_funcao)para definir a função aplicada nas colunas.

Usando across(), podemos facilmente aplicar uma função em todas as colunas da nossa base. Abaixo, calculamos o número de valores distintos para todas as variáveis da base casas. O padrão do parâmetro .cols é everithing(), que representa “todas as colunas”.

casas %>% 
  summarise(across(.fns = n_distinct))

Para fazer essa mesma operação, antes utilizaríamos a função summarise_all().

casas %>% 
  summarise_all(.funs = ~n_distinct(.x))

Se quisermos selecionar as colunas a serem modificadas a partir de um teste lógico, utilizamos o ajudante where().

No exemplo abaixo, calculamos o número de valores distintos das colunas de categóricas.

casas %>%
  summarise(across(where(is.character), n_distinct))

Todas as colunas da base resultante eram colunas com classe character na base casas.

Anteriormente, utilizávamos a função summarise_if().

casas %>%
  summarise_if(is.character, n_distinct)

Com o across(), podemos combinar os efeitos de um summarise_if() e um summarise_at() em um único summarise(). A seguir, calculamos as áreas médias, garantindo que pegamos apenas variáveis numéricas.

casas %>%
  summarise(across(where(is.numeric) & contains("_area"), mean, na.rm = TRUE))

Além disso, com a função across(), podemos fazer sumarizações complexas que não seria possível utilizando apenas as funções summarise(), summarise_if() e summarise_at(). No exemplo a seguir, calculamos a média das áreas, o número de NAs de variáveis categóricas e o número de casas para cada tipo de fundação. Tudo em um mesmo summarise()!

casas %>%
  group_by(fundacao_tipo) %>%
  summarise(
    across(where(is.numeric) & contains("area"), mean, na.rm = TRUE),
    across(where(is.character), ~sum(is.na(.x))),
    n_obs = n(),
  ) %>% 
  select(1:4, n_obs)

Embora a nova sintaxe, usando across(), não seja muito diferente do que fazíamos antes, realizar sumarizações complexas não é a única vantagem desse novo framework.

O across() pode ser utilizado em todos os verbos do {dplyr} (com exceção do select() e rename(), já que ele não trás vantagens com relação ao que já podia ser feito) e isso unifica o modo de fazermos essas operações no dplyr. Em vez de termos uma família de funções para cada verbo, temos agora apenas o próprio verbo e a função across().

Vamos ver um exemplo para o mutate() e para o filter().

O código abaixo transforma todas as variáveis que possuem “area” no nome, passando os valores de pés quadrados para metros quadrados.

casas %>%
  mutate(across(
    contains("area"),
    ~ .x / 10.764
  ))

Já o código a seguir filtra apenas as casas que possuem varanda aberta, cerca e lareira (o NA nessa base significa que a casa não possui tal característica).

casas %>%
  filter(across(
    c(varanda_aberta_area, cerca_qualidade, lareira_qualidade),
    ~!is.na(.x)
  )) 

Não precisamos do across() na hora de selecionar colunas. A função select() já usa naturalmente o mecanismo de seleção de colunas que o across() proporciona.

casas %>%
  select(where(is.numeric))

O mesmo vale para o rename(). Se quisermos renomer várias colunas, a partir de uma função, utilizamos o rename_with().

casas %>%
  rename_with(toupper, contains("venda"))

A função relocate()

O {dplyr} possui agora uma função própria para reorganizar colunas: relocate(). Por padrão, ela coloca uma ou mais colunas no começo da base.

casas %>%
  relocate(venda_valor, venda_tipo)

Podemos usar os argumentos .after e .before para fazer mudanças mais complexas.

O código baixo coloca a coluna venda_ano depois da coluna construcao_ano.

casas %>%
  relocate(venda_ano, .after = construcao_ano)

O código baixo coloca a coluna venda_ano antes da coluna construcao_ano.

casas %>%
  relocate(venda_ano, .before = construcao_ano)

A função rowwise()

Por fim, vamos discutir operações feitas por linha. Tome como exemplo a tabela abaixo. Ela apresenta as notas de alunos em quatro provas.

tab_notas <- tibble(
  student_id = 1:5,
  prova1 = sample(0:10, 5),
  prova2 = sample(0:10, 5),
  prova3 = sample(0:10, 5),
  prova4 = sample(0:10, 5)
)
tab_notas

Se quisermos gerar uma coluna com a nota média de cada aluno nas quatro provas, não poderíamos usar o mutate() diretamente.

tab_notas %>% mutate(media = mean(c(prova1, prova2, prova3, prova4)))

Neste caso, todas as colunas estão sendo empilhadas e gerando uma única média, passada a todas as linhas da coluna media.

Para fazermos a conta para cada aluno, podemos agrupar por aluno. Agora sim a média é calculada apenas nas notas de cada estudante.

tab_notas %>%
  group_by(student_id) %>%
  mutate(media = mean(c(prova1, prova2, prova3, prova4)))

Também podemos nos aproveitar da sintaxe do across() neste caso. Para isso, precisamos substutir a função c() pela função c_across().

tab_notas %>%
  group_by(student_id) %>%
  mutate(media = mean(c_across(starts_with("prova"))))

Equivalentemente ao group_by(), neste caso, podemos usar a função rowwise().

tab_notas %>%
  rowwise(student_id) %>%
  mutate(media = mean(c_across(starts_with("prova"))))

Ela é muito útil quando queremos fazer operação por linhas, mas não temos uma coluna de identificação. Por padrão, se não indicarmos nenhuma coluna, cada linha será um “grupo”.

tab_notas %>%
  rowwise() %>%
  mutate(media = mean(c_across(starts_with("prova"))))

Veja que student_id não é passada para a função rowwise(). Não precisaríamos dessa coluna na base para reproduzir a geração da columa media neste caso.

Exercícios

A base casas abaixo pode ser encontrada a partir do código abaixo:

remotes::install_github("cienciadedatos/dados")
library(dados)
dados::casas

1. Reescreva os códigos abaixo utilizando as funções across() e where().

a.

casas %>%
  group_by(geral_qualidade) %>%
  summarise(
    acima_solo_area_media = mean(acima_solo_area, na.rm = TRUE),
    garagem_area_media = mean(garagem_area, na.rm = TRUE),
    valor_venda_medio = mean(venda_valor, na.rm = TRUE)
  )

b.

casas %>%
  filter_at(
    vars(porao_qualidade, varanda_fechada_area, cerca_qualidade),
    ~!is.na(.x)
  )

c.

casas %>%
  mutate_if(is.character, ~tidyr::replace_na(.x, replace = "Não possui"))

2. Utilizando a base casas, resolva os itens a seguir.

  • a. Usando o case_when() crie um código para categorizar a variável venda_valor da seguinte maneira:

    • barata: $0 a $129.500
    • preço mediano: $129.500 a $180.796
    • cara: $ 180.796 a $213.500
    • muito cara: maior que $213.500
  • b. Utilize o código feito na letra (a) para agrupar a base casas pela variável venda_valor categorizada e calcular todas as áreas médias para cada uma dessas categorias.

3. Escreva um código que receba a base casas e retorne uma tabela com apenas

  • a. as colunas referentes à garagem da casa.

  • b. as colunas referentes a variáveis de qualidade.

  • c. colunas numéricas que representam áreas da casa e do terreno.

  • d. colunas numéricas.

  • e. colunas referentes à piscina, porão e o valor de venda.

4. Usando a função rename_with(), troque todos os "_" dos nomes das colunas por um espaço " ".

5. Escreva um código para colocar todas as colunas relativas a venda no começo da base casas.

6. 5. Escreva um código para colocar todas as colunas numéricas da base casas no começo da tabela e todas as colunas categóricas no final.

LS0tDQp0aXRsZTogIkFwcmVuZGVuZG8gYSB1c2FyIG8gRHBseXIiDQpydW5uaW5naGVhZGVyOiAiIiAjIG9ubHkgZm9yIHBkZiBvdXRwdXQNCnN1YnRpdGxlOiAiTcOpdG9kb3MgQ29tcHV0YWNpb25haXMgZW0gUiIgIyBvbmx5IGZvciBodG1sIG91dHB1dA0KYXV0aG9yOiAiUHJvZi4gQ2xhdWRpYW5vIE5ldG8iDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQplbmNvZGluZzogImlzby04ODU5LTEiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQoNCiMgTyBwYWNvdGUgZHBseXIgeyNkcGx5cn0NCg0KTyBgZHBseXJgIMOpIG8gcGFjb3RlIG1haXMgw7p0aWwgcGFyYSByZWFsaXphciB0cmFuc2Zvcm1hw6fDo28gZGUgZGFkb3MsIGFsaWFuZG8gc2ltcGxpY2lkYWRlIGUgZWZpY2nDqm5jaWEgZGUgdW1hIGZvcm1hIGVsZWdhbnRlLiBPcyBzY3JpcHRzIGVtIFIgcXVlIGZhemVtIHVzbyBpbnRlbGlnZW50ZSBkb3MgdmVyYm9zIGBkcGx5cmAgZSBhcyBmYWNpbGlkYWRlcyBkbyBvcGVyYWRvciBfcGlwZV8gdGVuZGVtIGEgZmljYXIgbWFpcyBsZWfDrXZlaXMgZSBvcmdhbml6YWRvcyBzZW0gcGVyZGVyIHZlbG9jaWRhZGUgZGUgZXhlY3XDp8Ojby4NCg0KQXMgcHJpbmNpcGFpcyBmdW7Dp8O1ZXMgZG8gYGRwbHlyYCBzw6NvOg0KDQotIGBzZWxlY3QoKWAgLSBzZWxlY2lvbmEgY29sdW5hcw0KLSBgYXJyYW5nZSgpYCAtIG9yZGVuYSBhIGJhc2UNCi0gYGZpbHRlcigpYCAtIGZpbHRyYSBsaW5oYXMNCi0gYG11dGF0ZSgpYCAtIGNyaWEvbW9kaWZpY2EgY29sdW5hcw0KLSBgZ3JvdXBfYnkoKWAgLSBhZ3J1cGEgYSBiYXNlDQotIGBzdW1tYXJpc2UoKWAgLSBzdW1hcml6YSBhIGJhc2UNCg0KVG9kYXMgZXNzYXMgZnVuw6fDtWVzIHNlZ3VlbSBhcyBtZXNtYXMgY2FyYWN0ZXLDrXN0aWNhczoNCg0KLSBPIF9pbnB1dF8gIMOpIHNlbXByZSB1bWEgYHRpYmJsZWAgZSBvIF9vdXRwdXRfICDDqSBzZW1wcmUgdW0gYHRpYmJsZWAuDQotIENvbG9jYW1vcyBhIGB0aWJibGVgIG5vIHByaW1laXJvIGFyZ3VtZW50byBlIG8gcXVlIHF1ZXJlbW9zIGZhemVyIG5vcyBvdXRyb3MgYXJndW1lbnRvcy4NCi0gQSB1dGlsaXphw6fDo28gw6kgZmFjaWxpdGFkYSBjb20gbyBlbXByZWdvIGRvIG9wZXJhZG9yIGAlPiVgLg0KDQpBcyBwcmluY2lwYWlzIHZhbnRhZ2VucyBkZSBzZSB1c2FyIG8gYGRwbHlyYCBlbSBkZXRyaW1lbnRvIGRhcyBmdW7Dp8O1ZXMgZG8gUiBiYXNlIHPDo286DQoNCi0gTWFuaXB1bGFyIGRhZG9zIHNlIHRvcm5hIHVtYSB0YXJlZmEgbXVpdG8gbWFpcyBzaW1wbGVzLg0KLSBPIGPDs2RpZ28gZmljYSBtYWlzIGludHVpdGl2byBkZSBzZXIgZXNjcml0byBlIG1haXMgc2ltcGxlcyBkZSBzZXIgbGlkby4NCi0gTyBwYWNvdGUgYGRwbHlyYCB1dGlsaXphIGBDYCBlIGBDKytgIHBvciB0csOhcyBkYSBtYWlvcmlhIGRhcyBmdW7Dp8O1ZXMsIG8gcXVlIGdlcmFsbWVudGUgdG9ybmEgbyBjw7NkaWdvIG1haXMgcsOhcGlkby4NCi0gw4kgcG9zc8OtdmVsIHRyYWJhbGhhciBjb20gZGlmZXJlbnRlcyBmb250ZXMgZGUgZGFkb3MsIGNvbW8gYmFzZXMgcmVsYWNpb25haXMgKFNRTCkgZSBgZGF0YS50YWJsZWAuDQoNClNlIHZvY8OqIGFpbmRhIG7Do28gdGl2ZXIgbyBgZHBseXJgIGluc3RhbGFkbywgcm9kZSBvIGPDs2RpZ28gYWJhaXhvLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQpsaWJyYXJ5KGRwbHlyKQ0KYGBgDQoNCk5lc3RlIGNhcMOtdHVsbywgdmFtb3MgdHJhYmFsaGFyIGNvbSB1bWEgYmFzZSBkZSBmaWxtZXMgZG8gSU1EQi4NCg0KQXNzaW0sIHV0aWxpemFyZW1vcyBvIG9iamV0byBgaW1kYmAgcGFyYSBhY2Vzc2FyIG9zIGRhZG9zLg0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkoZHBseXIpDQppbWRiIDwtIHJlYWRyOjpyZWFkX3JkcygiaHR0cHM6Ly9naXRodWIuY29tL2N1cnNvLXIvbGl2cm8tbWF0ZXJpYWwvcmF3L21hc3Rlci9hc3NldHMvZGF0YS9pbWRiLnJkcyIpDQpgYGANCg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCmltZGINCmBgYA0KDQoNCkFnb3JhLCB2YW1vcyBhdmFsaWFyIGNvbSBtYWlzIGRldGFsaGVzIGFzIHByaW5jaXBhaXMgZnVuw6fDtWVzIGRvIHBhY290ZSBgZHBseXJgLg0KDQojIyMgU2VsZWNpb25hbmRvIGNvbHVuYXMNCg0KUGFyYSBzZWxlY2lvbmFyIGNvbHVuYXMsIHV0aWxpemFtb3MgYSBmdW7Dp8OjbyBgc2VsZWN0KClgLg0KDQpPIHByaW1laXJvIGFyZ3VtZW50byBkYSBmdW7Dp8OjbyDDqSBhIGJhc2UgZGUgZGFkb3MgZSBvcyBkZW1haXMgYXJndW1lbnRvcyBzw6NvIG9zIG5vbWVzIGRhcyBjb2x1bmFzIHF1ZSB2b2PDqiBnb3N0YXJpYSBkZSBzZWxlY2lvbmFyLiBSZXBhcmUgcXVlIHZvY8OqIG7Do28gcHJlY2lzYSBjb2xvY2FyIG8gbm9tZSBkYSBjb2x1bmEgZW50cmUgYXNwYXMuDQoNCmBgYHtyfQ0Kc2VsZWN0KGltZGIsIHRpdHVsbykNCmBgYA0KDQoNClZvY8OqIHRhbWLDqW0gcG9kZSBzZWxlY2lvbmFyIHbDoXJpYXMgY29sdW5hcy4NCg0KYGBge3J9DQpzZWxlY3QoaW1kYiwgdGl0dWxvLCBhbm8sIG9yY2FtZW50bykNCmBgYA0KDQpPIG9wZXJhZG9yIGA6YCDDqSBtdWl0byDDunRpbCBwYXJhIHNlbGVjaW9uYXIgY29sdW5hcyBjb25zZWN1dGl2YXMuDQoNCmBgYHtyfQ0Kc2VsZWN0KGltZGIsIHRpdHVsbzpjb3IpDQpgYGANCg0KDQpPIGBkcGx5cmAgcG9zc3VpIG8gY29uanVudG8gZGUgZnVuw6fDtWVzIGF1eGlsaWFyZXMgbXVpdG8gw7p0ZWlzIHBhcmEgc2VsZcOnw6NvIGRlIGNvbHVuYXMuIEFzIHByaW5jaXBhaXMgc8OjbzoNCg0KLSBgc3RhcnRzX3dpdGgoKWA6IHBhcmEgY29sdW5hcyBxdWUgY29tZcOnYW0gY29tIHVtIHRleHRvIHBhZHLDo28NCi0gYGVuZHNfd2l0aCgpYDogcGFyYSBjb2x1bmFzIHF1ZSB0ZXJtaW5hbSBjb20gdW0gdGV4dG8gcGFkcsOjbw0KLSBgY29udGFpbnMoKWA6IHBhcmEgY29sdW5hcyBxdWUgY29udMOqbSB1bSB0ZXh0byBwYWRyw6NvDQoNClNlbGVjaW9uYW1vcyBhIHNlZ3VpciB0b2RhcyBhcyBjb2x1bmFzIHF1ZSBjb21lw6dhbSBjb20gbyB0ZXh0byAiYXRvciIuDQoNCmBgYHtyfQ0Kc2VsZWN0KGltZGIsIHN0YXJ0c193aXRoKCJhdG9yIikpDQpgYGANCg0KUGFyYSByZXRpcmFyIGNvbHVuYXMgZGEgYmFzZSwgYmFzZSBhY3Jlc2NlbnRhciB1bSBgLWAgYW50ZXMgZGEgc2VsZcOnw6NvLg0KDQpgYGB7cn0NCmltZGIgJT4lDQogIHNlbGVjdCgtYW5vLCAtIGRpcmV0b3IpDQppbWRiICU+JQ0KICBzZWxlY3QoLXN0YXJ0c193aXRoKCJhdG9yIikpDQpgYGANCg0KIyMjIyBFeGVyY8OtY2lvcyB7LX0NCg0KVXRpbGl6ZSBhIGJhc2UgYGltZGJgIG5vcyBleGVyY8OtY2lvcyBhIHNlZ3Vpci4NCg0KKioxLioqIFRlc3RlIGFwbGljYXIgYSBmdW7Dp8OjbyBgZ2xpbXBzZSgpYCBkbyBwYWNvdGUgYHtkcGx5cn1gIMOgIGJhc2UgYGltZGJgLiBPIHF1ZSBlbGEgZmF6Pw0KDQoqKjIuKiogQ3JpZSB1bWEgdGFiZWxhIGNvbSBhcGVuYXMgYXMgY29sdW5hcyBgdGl0dWxvYCwgYGRpcmV0b3JgLCBlIGBvcmNhbWVudG8uYCBTYWx2ZSBlbSB1bSBvYmpldG8gY2hhbWFkbyBgaW1kYl9zaW1wbGVzYC4NCg0KKiozLioqIFNlbGVjaW9uZSBhcGVuYXMgYXMgY29sdW5hcyBgYXRvcl8xYCwgYGF0b3JfMmAgZSBgYXRvcl8zYCB1c2FuZG8gbyBhanVkYW50ZSBgY29udGFpbnMoKWAuDQoNCioqNC4qKiBVc2FuZG8gYSBmdW7Dp8OjbyBgc2VsZWN0KClgIChlIHNldXMgYWp1ZGFudGVzKSwgZXNjcmV2YSBjw7NkaWdvcyBxdWUgcmV0b3JuZW0gYSBiYXNlIElNREIgc2VtIGFzIGNvbHVuYXMgYGF0b3JfMWAsIGBhdG9yXzJgIGUgYGF0b3JfMy5gIEVzY3JldmEgdG9kYXMgYXMgc29sdcOnw7VlcyBkaWZlcmVudGVzIHF1ZSB2b2PDqiBjb25zZWd1aXIgcGVuc2FyLiANCg0KIyMjIE9yZGVuYW5kbyBhIGJhc2UNCg0KUGFyYSBvcmRlbmFyIGxpbmhhcywgdXRpbGl6YW1vcyBhIGZ1bsOnw6NvIGBhcnJhbmdlKClgLiBPIHByaW1laXJvIGFyZ3VtZW50byDDqSBhIGJhc2UgZGUgZGFkb3MuIE9zIGRlbWFpcyBhcmd1bWVudG9zIHPDo28gYXMgY29sdW5hcyBwZWxhcyBxdWFpcyBxdWVyZW1vcyBvcmRlbmFyIGFzIGxpbmhhcy4gTm8gZXhlbXBsbyBhIHNlZ3Vpciwgb3JkZW5hbW9zIGFzIGxpbmhhcyBkYSBiYXNlIHBvciBvcmRlbSBjcmVzY2VudGUgZGUgb3LDp2FtZW50by4NCg0KYGBge3J9DQphcnJhbmdlKGltZGIsIG9yY2FtZW50bykNCmBgYA0KDQpUYW1iw6ltIHBvZGVtb3Mgb3JkZW5hciBkZSBmb3JtYSBkZWNyZXNjZW50ZSB1c2FuZG8gYSBmdW7Dp8OjbyBgZGVzYygpYC4NCg0KYGBge3J9DQphcnJhbmdlKGltZGIsIGRlc2Mob3JjYW1lbnRvKSkNCmBgYA0KDQoNCkUgY2xhcm8sIG9yZGVuYXIgc2VndW5kbyBkdWFzIG91IG1haXMgY29sdW5hcy4NCg0KYGBge3J9DQphcnJhbmdlKGltZGIsIGRlc2MoYW5vKSwgZGVzYyhvcmNhbWVudG8pKQ0KYGBgDQoNCiMjIyMgRXhlcmPDrWNpb3Mgey19DQoNClV0aWxpemUgYSBiYXNlIGBpbWRiYCBub3MgZXhlcmPDrWNpb3MgYSBzZWd1aXIuDQoNCioqMS4qKiBPcmRlbmUgb3MgZmlsbWVzIGVtIG9yZGVtIGNyZXNjZW50ZSBkZSBgYW5vYCBlIGRlY3Jlc2NlbnRlIGRlIGByZWNlaXRhYCBlIHNhbHZlIGVtIHVtIG9iamV0byBjaGFtYWRvIGBmaWxtZXNfb3JkZW5hZG9zYC4NCg0KKioyLioqIFNlbGVjaW9uZSBhcGVuYXMgYXMgY29sdW5hcyBgdGl0dWxvYCBlIGBvcmNhbWVudG9gIGUgZW50w6NvIG9yZGVuZSBkZSBmb3JtYSBkZWNyZXNjZW50ZSBwZWxvIGBvcmNhbWVudG8uYA0KDQojIyMgTyBwaXBlIGVtIGHDp8Ojbw0KDQpOYSBncmFuZGUgbWFpb3JpYSBkb3MgY2Fzb3MsIHZhbW9zIGFwbGljYXIgbWFpcyBkZSB1bWEgZnVuw6fDo28gZGUgbWFuaXB1bGHDp8OjbyBlbSB1bWEgYmFzZSBwYXJhIG9idGVybW9zIGEgdGFiZWxhIHF1ZSBkZXNlamFtb3MuIFBvZGVyw61hbW9zLCBwb3IgZXhlbXBsbywgcXVlcmVyIHVtYSB0YWJlbGEgYXBlbmFzIGNvbSBvIHTDrXR1bG8gZSBhbm8gZG9zIGZpbG1lcywgb3JkZW5hZGEgZGUgZm9ybWEgY3Jlc2NlbnRlIGRlIGxhbsOnYW1lbnRvLiBQYXJhIGZhemVyIGlzc28sIHBvZGVyw61hbW9zIGFuaW5oYXIgYXMgZnVuw6fDtWVzDQoNCmBgYHtyfQ0KYXJyYW5nZShzZWxlY3QoaW1kYiwgdGl0dWxvLCBhbm8pLCBhbm8pDQpgYGANCg0Kb3UgY3JpYXIgdW0gb2JqZXRvIGludGVybWVkacOhcmlvIA0KDQpgYGB7cn0NCnRhYl90aXR1bG9fYW5vIDwtIHNlbGVjdChpbWRiLCB0aXR1bG8sIGFubykNCmFycmFuZ2UodGFiX3RpdHVsb19hbm8sIGFubykNCmBgYA0KDQpPcyBkb2lzIGPDs2RpZ29zIGZ1bmNpb25hbSBlIGxldmFtIGFvIG1lc21vIHJlc3VsdGFkbywgbWFzIG7Do28gc8OjbyBtdWl0byBib2FzLg0KDQpBIHByaW1laXJhIGFsdGVybmF0aXZhIMOpIHJ1aW0gZGUgZXNjcmV2ZXIsIGrDoSBxdWUgcHJlY2lzYW1vcyBlc2NyZXZlciBwcmltZWlybyBhIGZ1bsOnw6NvIHF1ZSByb2RhIHBvciDDumx0aW1vLCBlIGRlIGxlciwgcG9pcyDDqSBkaWbDrWNpbCBpZGVudGlmaWNhciBxdWFsIGFyZ3VtZW50byBwZXJ0ZW5jZSBhIHF1YWwgZnVuw6fDo28uDQoNCkEgc2VndW5kYSBhbHRlcm5hdGl2YSDDqSBydWltIHBvaXMgZXhpZ2UgYSBjcmlhw6fDo28gZGUgb2JqZXRvcyBhdXhpbGlhcmVzLiBTZSBxdWlzw6lzc2ltb3MgYXBsaWNhciAxMCBvcGVyYcOnw7VlcyBuYSBiYXNlLCBwcmVjaXNhcsOtYW1vcyBjcmlhciA5IG9iamV0b3MgaW50ZXJtZWRpw6FyaW9zLg0KDQpBIHNvbHXDp8OjbyBwYXJhIGFwbGljYXIgZGl2ZXJzYXMgb3BlcmHDp8O1ZXMgZGUgbWFuaXB1bGHDp8OjbyBlbSB1bWEgYmFzZSBkZSBkYWRvcyDDqSBhcGxpY2FyIG8gb3BlcmFkb3IgcGlwZTogYCU+JWAuDQoNCmBgYHtyfQ0KaW1kYiAlPiUgDQogIHNlbGVjdCh0aXR1bG8sIGFubykgJT4lIA0KICBhcnJhbmdlKGFubykNCmBgYA0KDQpPIHF1ZSBlc3TDoSBzZW5kbyBmZWl0byBubyBjw7NkaWdvIGNvbSBwaXBlPyBEYSBwcmltZWlyYSBwYXJhIGEgc2VndW5kYSBsaW5oYSwgZXN0YW1vcyBhcGxpY2FuZG8gYSBmdW7Dp8OjbyBgc2VsZWN0KClgIMOgIGJhc2UgaW1kYi4gRGEgc2VndW5kYSBwYXJhIGEgdGVyY2VpcmEsIGVzdGFtb3MgYXBsaWNhbmRvIGEgZnVuw6fDo28gYGFycmFuZ2UoKWAgw6AgYmFzZSByZXN1bHRhbnRlIGRhIGZ1bsOnw6NvIGBzZWxlY3QoKWAuDQoNCk8gcmVzdWx0YWRvIGRlc3NlIGPDs2RpZ28gw6kgaWRlbnRpY28gw6BzIHRlbnRhdGl2YXMgc2VtIHBpcGUsIGNvbSBhIHZhbnRhZ2VtIGRlIHRlcm1vcyBlc2NyaXRvIG8gY8OzZGlnbyBuYSBvcmRlbSBlbSBxdWUgYXMgZnVuw6fDtWVzIHPDo28gYXBsaWNhZGFzLCBkZSB0ZXJtb3MgdW0gY8OzZGlnbyBtdWl0byBtYWlzIGxlZ8OtdmVsIGUgZGUgbsOjbyBwcmVjaXNhcm1vcyB1dGlsaXphciBvYmpldG9zIGludGVybWVkacOhcmlvcy4NCg0KIyMjIEZpbHRyYW5kbyBsaW5oYXMNCg0KUGFyYSBmaWx0cmFyIHZhbG9yZXMgZGUgdW1hIGNvbHVuYSBkYSBiYXNlLCB1dGlsaXphbW9zIGEgZnVuw6fDo28gYGZpbHRlcigpYC4NCg0KYGBge3J9DQppbWRiICU+JSBmaWx0ZXIobm90YV9pbWRiID4gOSkNCmBgYA0KDQoNClBvZGVtb3Mgc2VsZWNpb25hciBhcGVuYXMgYXMgY29sdW5hcyB0w610dWxvIGUgbm90YSBwYXJhIHZpc3VhbGl6YXJtb3MgYXMgbm90YXM6DQoNCmBgYHtyfQ0KaW1kYiAlPiUgDQogIGZpbHRlcihub3RhX2ltZGIgPiA5KSAlPiUgDQogIHNlbGVjdCh0aXR1bG8sIG5vdGFfaW1kYikNCmBgYA0KDQpQb2RlbW9zIGVzdGVuZGVyIG8gZmlsdHJvIHBhcmEgZHVhcyBvdSBtYWlzIGNvbHVuYXMuIFBhcmEgaXNzbywgc2VwYXJhbW9zIGNhZGEgb3BlcmHDp8OjbyBwb3IgdW1hIHbDrXJndWxhLg0KDQpgYGB7cn0NCmltZGIgJT4lIGZpbHRlcihhbm8gPiAyMDEwLCBub3RhX2ltZGIgPiA4LjUpDQpgYGANCg0KVGFtYsOpbSBwb2RlbW9zIGZhemVyIG9wZXJhw6fDtWVzIGNvbSBhcyBjb2x1bmFzIGRhIGJhc2UgZGVudHJvIGRhIGZ1bsOnw6NvIGZpbHRlci4gTyBjw7NkaWdvIGFiYWl4byBkZXZvbHZlIHVtYSB0YWJlbGEgYXBlbmFzIGNvbSBvcyBmaWxtZXMgcXVlIGx1Y3JhcmFtLg0KDQpgYGB7cn0NCmltZGIgJT4lIGZpbHRlcihyZWNlaXRhIC0gb3JjYW1lbnRvID4gMCkNCmBgYA0KDQpOYXR1cmFsbWVudGUsIHBvZGVtb3MgZmlsdHJhciBjb2x1bmFzIGNhdGVnw7NyaWNhcy4gTyBleGVtcGxvIGFiYWl4byByZXRvcm5hIHVtYSB0YWJlbGEgYXBlbmFzIGNvbSBvcyBmaWxtZXMgY29tIGEgQW5nZWxpbmEgSm9saWUgUGl0dCBvdSBvIEJyYWQgUGl0dCBubyBwYXBlbCBwcmluY2lwYWwuDQoNCmBgYHtyfQ0KaW1kYiAlPiUNCiAgZmlsdGVyKGF0b3JfMSAlaW4lIGMoJ0FuZ2VsaW5hIEpvbGllIFBpdHQnLCAiQnJhZCBQaXR0IikpDQpgYGANCg0KUGFyYSBmaWx0cmFyIHRleHRvcyBzZW0gY29ycmVzcG9uZMOqbmNpYSBleGF0YSwgcG9kZW1vcyB1dGlsaXphciBhIGZ1bsOnw6NvIGF1eGlsaWFyIGBzdHJfZGV0ZWN0KClgIGRvIHBhY290ZSBge3N0cmluZ3J9YC4gRWxhIHNlcnZlIHBhcmEgdmVyaWZpY2FyIHNlIGNhZGEgc3RyaW5nIGRlIHVtIHZldG9yIGNvbnTDqW0gdW0gZGV0ZXJtaW5hZG8gcGFkcsOjbyBkZSB0ZXh0by4NCg0KYGBge3J9DQpsaWJyYXJ5KHN0cmluZ3IpDQpzdHJfZGV0ZWN0KA0KICBzdHJpbmcgPSBjKCJhIiwgImFhIiwiYWJjIiwgImJjIiwgIkEiLCBOQSksIA0KICBwYXR0ZXJuID0gImEiDQopDQpgYGANCg0KUG9kZW1vcyB1dGlsaXrDoS1sYSBwYXJhIGZpbHRyYXIgYXBlbmFzIG9zIGZpbG1lcyBxdWUgY29udMOqbSBvIGfDqm5lcm8gYcOnw6NvLg0KDQpgYGB7cn0NCiMgQSBjb2x1bmEgZ8OqbmVyb3MgYXByZXNlbnRhIHRvZG9zIG9zIGfDqm5lcm9zIGRvcyBmaWxtZXMgY29uY2F0ZW5hZG9zDQppbWRiJGdlbmVyb3NbMTo2XQ0KIyBQb2RlbW9zIGRldGVjdGFyIHNlIG8gZ8OqbmVybyBBY3Rpb24gYXBhcmVjZSBuYSBzdHJpbmcNCnN0cl9kZXRlY3QoDQogIHN0cmluZyA9IGltZGIkZ2VuZXJvc1sxOjZdLA0KICBwYXR0ZXJuID0gIkFjdGlvbiINCikNCiMgQXBsaWNhbW9zIGVzc2EgbMOzZ2ljYSBkZW50cm8gZGEgZnVuw6fDo28gZmlsdGVyLCBwYXJhIGEgY29sdW5hIGNvbXBsZXRhDQppbWRiICU+JSBmaWx0ZXIoc3RyX2RldGVjdChnZW5lcm9zLCAiQWN0aW9uIikpDQpgYGANCg0KIyMjIyBFeGVyY8OtY2lvcyB7LX0NCg0KVXRpbGl6ZSBhIGJhc2UgYGltZGJgIG5vcyBleGVyY8OtY2lvcyBhIHNlZ3Vpci4NCg0KKioxLioqIENyaWUgdW0gb2JqZXRvIGNoYW1hZG8gYGZpbG1lc19wYmAgYXBlbmFzIGNvbSBmaWxtZXMgcHJldG8gZSBicmFuY28uDQoNCioqMi4qKiBDcmllIHVtIG9iamV0byBjaGFtYWRvIGBjdXJ0b3NfbGVnYWlzYCBjb20gZmlsbWVzIGRlIDkwIG1pbnV0b3Mgb3UgbWVub3MgZGUgZHVyYcOnw6NvIGUgbm90YSBubyBpbWRiIG1haW9yIGRvIHF1ZSA4LjUuDQoNCioqMy4qKiBSZXRvcm5lIHRhYmVsYXMgKGB0aWJibGVzYCkgYXBlbmFzIGNvbToNCg0KLSAqKmEuKiogZmlsbWVzIGNvbG9yaWRvcyBhbnRlcmlvcmVzIGEgMTk1MDsNCg0KLSAqKmIuKiogZmlsbWVzIGRvICJXb29keSBBbGxlbiIgb3UgZG8gIldlcyBBbmRlcnNvbiI7DQoNCi0gKipjLioqIGZpbG1lcyBkbyAiU3RldmVuIFNwaWVsYmVyZyIgb3JkZW5hZG9zIGRlIGZvcm1hIGRlY3Jlc2NlbnRlIHBvciBhbm8sIG1vc3RyYW5kbyBhcGVuYXMgYXMgY29sdW5hcyBgdGl0dWxvYCBlIGBhbm9gOw0KDQotICoqZC4qKiAgZmlsbWVzIHF1ZSB0ZW5oYW0gIkFjdGlvbiIgKipvdSoqICJDb21lZHkiIGVudHJlIG9zIHNldXMgZ8OqbmVyb3M7DQoNCi0gKiplLioqIGZpbG1lcyBxdWUgdGVuaGFtICJBY3Rpb24iICoqZSoqICJDb21lZHkiIGVudHJlIG9zIHNldXMgZ8OqbmVyb3MgZSB0ZW5oYSBgbm90YV9pbWRiYCBtYWlvciBxdWUgODsNCg0KLSAqKmYuKiogZmlsbWVzIHF1ZSBuw6NvIHBvc3N1ZW0gaW5mb3JtYcOnw6NvIHRhbnRvIGRlIHJlY2VpdGEgcXVhbnRvIGRlIG9yw6dhbWVudG8gKGlzdG8gw6ksIHBvc3N1ZW0gYE5BYCBlbSBhbWJhcyBhcyBjb2x1bmFzKS4NCg0KDQojIyMgTW9kaWZpY2FuZG8gZSBjcmlhbmRvIG5vdmFzIGNvbHVuYXMNCg0KUGFyYSBtb2RpZmljYXIgdW1hIGNvbHVuYSBleGlzdGVudGUgb3UgY3JpYXIgdW1hIG5vdmEgY29sdW5hLCB1dGlsaXphbW9zIGEgZnVuw6fDo28gYG11dGF0ZSgpYC4gTyBjw7NkaWdvIGFiYWl4byBkaXZpZGUgb3MgdmFsb3JlcyBkYSBjb2x1bmEgZHVyYcOnw6NvIHBvciA2MCwgbXVkYW5kbyBhIHVuaWRhZGUgZGUgbWVkaWRhIGRlc3NhIHZhcmnDoXZlbCBkZSBtaW51dG9zIHBhcmEgaG9yYXMuDQoNCmBgYHtyfQ0KaW1kYiAlPiUgbXV0YXRlKGR1cmFjYW8gPSBkdXJhY2FvLzYwKQ0KYGBgDQoNClRhbWLDqW0gcG9kZXLDrWFtb3MgdGVyIGNyaWFkbyBlc3NhIHZhcmnDoXZlbCBlbSB1bWEgbm92YSBjb2x1bmEuIFJlcGFyZSBxdWUgYSBub3ZhIGNvbHVuYSBgZHVyYWNhb19ob3Jhc2Agw6kgY29sb2NhZGEgbm8gZmluYWwgZGEgdGFiZWxhLg0KDQpgYGB7cn0NCmltZGIgJT4lIG11dGF0ZShkdXJhY2FvX2hvcmFzID0gZHVyYWNhby82MCkNCmBgYA0KDQpQb2RlbW9zIGZhemVyIHF1YWxxdWVyIG9wZXJhw6fDo28gY29tIHVtYSBvdSBtYWlzIGNvbHVuYXMuIEEgw7puaWNhIHJlZ3JhIMOpIHF1ZSBvIHJlc3VsdGFkbyBkYSBvcGVyYcOnw6NvIHJldG9ybmUgdW0gdmV0b3IgY29tIGNvbXByaW1lbnRvIGlndWFsIGFvIG7Dum1lcm8gZGUgbGluaGFzIGRhIGJhc2UgKG91IGNvbSBjb21wcmltZW50byAxIHBhcmEgZGlzdHJpYnVpciB1bSBtZXNtbyB2YWxvciBlbSB0b2RhcyBhcyBsaW5oYXMpLiBWb2PDqiB0YW1iw6ltIHBvZGUgY3JpYXIvbW9kaWZpY2FyIHF1YW50YXMgY29sdW5hcyBxdWlzZXIgZGVudHJvIGRlIHVtIG1lc21vIGBtdXRhdGVgLg0KDQpgYGB7cn0NCmltZGIgJT4lIA0KICBtdXRhdGUobHVjcm8gPSByZWNlaXRhIC0gb3JjYW1lbnRvLCBwYWlzID0gIkVzdGFkb3MgVW5pZG9zIikgJT4lIA0KICBzZWxlY3QodGl0dWxvLCBsdWNybywgcGFpcykNCmBgYA0KDQojIyMjIEV4ZXJjw61jaW9zIHstfQ0KDQpVdGlsaXplIGEgYmFzZSBgaW1kYmAgbm9zIGV4ZXJjw61jaW9zIGEgc2VndWlyLg0KDQoqKjEuKiogQ3JpZSB1bWEgY29sdW5hIGNoYW1hZGEgYHByZWp1aXpvYCAoYG9yY2FtZW50byAtIHJlY2VpdGFgKSBlIHNhbHZlIGEgbm92YSB0YWJlbGEgZW0gdW0gb2JqZXRvIGNoYW1hZG8gYGltZGJfcHJlanVpem9gLiBFbSBzZWd1aWRhLCBmaWx0cmUgYXBlbmFzIG9zIGZpbG1lcyBxdWUgZGVyYW0gcHJlanXDrXpvIGUgb3JkZW5lIGEgdGFiZWxhIHBvciBvcmRlbSBjcmVzY2VudGUgZGUgcHJlanXDrXpvLg0KDQoqKjIuKiogRmF6ZW5kbyBhcGVuYXMgdW1hIGNoYW1hZGEgZGEgZnVuw6fDo28gbXV0YXRlKCksIGNyaWUgYXMgc2VndWludGVzIGNvbHVuYXMgbm92YXMgbmEgYmFzZSBgaW1kYmA6DQoNCi0gKiphLioqIGBsdWNybyA9IHJlY2VpdGEgLSBvcmNhbWVudG9gDQoNCi0gKipiLioqIGBsdWNyb19tZWRpb2ANCg0KLSAqKmMuKiogYGx1Y3JvX3JlbGF0aXZvID0gKGx1Y3JvIC0gbHVjcm9fbWVkaW8pL2x1Y3JvX21lZGlvYA0KDQotICoqZC4qKiBgaG91dmVfbHVjcm8gPSBpZmVsc2UobHVjcm8gPiAwLCAic2ltIiwgIm7Do28iKWANCg0KKiozLioqIENyaWUgdW1hIG5vdmEgY29sdW5hIHF1ZSBjbGFzc2lmaXF1ZSBvIGZpbG1lIGVtIGAicmVjZW50ZSJgIChwb3N0ZXJpb3IgYSAyMDAwKSBlIGAiYW50aWdvImAgKGRlIDIwMDAgcGFyYSB0csOhcykuDQoNCiMjIyBTdW1tYXJpc2FuZG8gYSBiYXNlDQoNClN1bWFyaXphw6fDo28gw6kgYSB0w6ljbmljYSBkZSBzZSByZXN1bWlyIHVtIGNvbmp1bnRvIGRlIGRhZG9zIHV0aWxpemFuZG8gYWxndW1hIG3DqXRyaWNhIGRlIGludGVyZXNzZS4gQSBtw6lkaWEsIGEgbWVkaWFuYSwgYSB2YXJpw6JuY2lhLCBhIGZyZXF1w6puY2lhLCBhIHByb3BvcsOnw6NvLCBwb3IgZXhlbXBsbywgc8OjbyB0aXBvcyBkZSBzdW1hcml6YcOnw6NvIHF1ZSB0cmF6ZW0gZGlmZXJlbnRlcyBpbmZvcm1hw6fDtWVzIHNvYnJlIHVtYSB2YXJpw6F2ZWwuIA0KDQpQYXJhIHN1bWFyaXphciB1bWEgY29sdW5hIGRhIGJhc2UsIHV0aWxpemFtb3MgYSBmdW7Dp8OjbyBgc3VtbWFyaXplKClgLiBPIGPDs2RpZ28gYWJhaXhvIHJlc3VtZSBhIGNvbHVuYSBvcsOnYW1lbnRvIHBlbGEgc3VhIG3DqWRpYS4NCg0KYGBge3J9DQppbWRiICU+JSBzdW1tYXJpemUobWVkaWFfb3JjYW1lbnRvID0gbWVhbihvcmNhbWVudG8sIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KUmVwYXJlIHF1ZSBhIHNhw61kYSBkYSBmdW7Dp8OjbyBjb250aW51YSBzZW5kbyB1bWEgdGliYmxlLg0KDQpQb2RlbW9zIGNhbGN1bGFyIGRpdmVyc2FzIHN1bWFyaXphw6fDtWVzIGRpZmVyZW50ZXMgZW0gdW0gbWVzbW8gYHN1bW1hcml6ZWAuIENhZGEgc3VtYXJpemHDp8OjbyBzZXLDoSB1bWEgY29sdW5hIGRhIG5vdmEgYmFzZS4NCg0KYGBge3J9DQppbWRiICU+JSBzdW1tYXJpc2UoDQogIG1lZGlhX29yY2FtZW50byA9IG1lYW4ob3JjYW1lbnRvLCBuYS5ybSA9IFRSVUUpLA0KICBtZWRpYW5hX29yY2FtZW50byA9IG1lZGlhbihvcmNhbWVudG8sIG5hLnJtID0gVFJVRSksDQogIHZhcmlhbmNpYV9vcmNhbWVudG8gPSB2YXIob3JjYW1lbnRvLCBuYS5ybSA9IFRSVUUpDQopDQpgYGANCg0KRSB0YW1iw6ltIHN1bWFyaXphciBkaXZlcnNhcyBjb2x1bmFzLg0KDQpgYGB7cn0NCmltZGIgJT4lIHN1bW1hcml6ZSgNCiAgbWVkaWFfb3JjYW1lbnRvID0gbWVhbihvcmNhbWVudG8sIG5hLnJtID0gVFJVRSksDQogIG1lZGlhX3JlY2VpdGEgPSBtZWFuKHJlY2VpdGEsIG5hLnJtID0gVFJVRSksDQogIG1lZGlhX2x1Y3JvID0gbWVhbihyZWNlaXRhIC0gb3JjYW1lbnRvLCBuYS5ybSA9IFRSVUUpDQopDQpgYGANCg0KTXVpdGFzIHZlemVzIHF1ZXJlbW9zIHN1bWFyaXphciB1bWEgY29sdW5hIGFncnVwYWRhIHBlbGFzIGNhdGVnb3JpYXMgZGUgdW1hIHNlZ3VuZGEgY29sdW5hLiBQYXJhIGlzc28sIGFsw6ltIGRvIGBzdW1tYXJpemVgLCB1dGlsaXphbW9zIHRhbWLDqW0gYSBmdW7Dp8OjbyBgZ3JvdXBfYnkoKWAuDQoNCk8gY8OzZGlnbyBhIHNlZ3VpciBjYWxjdWxhIGEgcmVjZWl0YSBtw6lkaWEgZG9zIGZpbG1lcyBwYXJhIGNhZGEgY2F0ZWdvcmlhIGRhIGNvbHVuYSAiY29yIi4NCg0KYGBge3J9DQppbWRiICU+JSANCiAgZ3JvdXBfYnkoY29yKSAlPiUgDQogIHN1bW1hcmlzZShyZWNlaXRhX21lZGlhID0gbWVhbihyZWNlaXRhLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQoNCkEgw7puaWNhIGFsdGVyYcOnw6NvIHF1ZSBhIGZ1bsOnw6NvIGBncm91cF9ieSgpYCBmYXogbmEgYmFzZSDDqSBhIG1hcmNhw6fDo28gZGUgcXVlIGEgYmFzZSBlc3TDoSBhZ3J1cGFkYS4NCg0KYGBge3J9DQppbWRiICU+JSBncm91cF9ieShjb3IpDQpgYGANCg0KIyMjIyBFeGVyY8OtY2lvcyB7LX0NCg0KVXRpbGl6ZSBhIGJhc2UgYGltZGJgIG5vcyBleGVyY8OtY2lvcyBhIHNlZ3Vpci4NCg0KKioxLioqIENhbGN1bGUgYSBkdXJhw6fDo28gbcOpZGlhIGUgbWVkaWFuYSBkb3MgZmlsbWVzIGRhIGJhc2UuDQoNCioqMi4qKiBDYWxjdWxlIG8gbHVjcm8gbcOpZGlvIGRvcyBmaWxtZXMgY29tIGR1cmHDp8OjbyBtZW5vciBxdWUgNjAgbWludXRvcy4NCg0KKiozLioqIEFwcmVzZW50ZSBuYSBtZXNtYSB0YWJlbGEgbyBsdWNybyBtw6lkaW8gZG9zIGZpbG1lcyBjb20gZHVyYWNhbyBtZW5vciBxdWUgNjAgbWludXRvcyBlIG8gbHVjcm8gbcOpZGlvIGRvcyBmaWxtZXMgY29tIGR1cmFjYW8gbWFpb3Igb3UgaWd1YWwgYSA2MCBtaW51dG9zLg0KDQoqKjQuKiogUmV0b3JuZSB0YWJlbGFzIChgdGliYmxlc2ApIGFwZW5hcyBjb206DQoNCi0gKiphLioqIGEgbm90YSBJTURCIG3DqWRpYSBkb3MgZmlsbWVzIHBvciB0aXBvIGRlIGNsYXNzaWZpY2FjYW87DQoNCi0gKipiLioqIGEgcmVjZWl0YSBtw6lkaWEgZSBtZWRpYW5hIGRvcyBmaWxtZXMgcG9yIGFubzsNCg0KLSAqKmMuKiogYXBlbmFzIG8gbm9tZSBkb3MgZGlyZXRvcmVzIGNvbSBtYWlzIGRlIDEwIGZpbG1lcy4NCg0KIyMjIEp1bnRhbmRvIGR1YXMgYmFzZXMNCg0KUG9kZW1vcyBqdW50YXIgZHVhcyB0YWJlbGFzIGEgcGFydGlyIGRlIHVtYSAoY29sdW5hKSBjaGF2ZSB1dGlsaXphbmRvIGEgZnVuw6fDo28gYGxlZnRfam9pbigpYC4gQ29tbyBleGVtcG8sIHZhbW9zIGluaWNpYWxtZW50ZSBjYWxjdWxhciBvIGx1Y3JvIG3DqWRpbyBkb3MgZmlsbWVzIGRlIGNhZGEgZGlyZXRvciBlIHNhbHZhciBubyBvYmpldG8gYHRhYl9sdWNyb19kaXJldG9yYC4NCg0KYGBge3J9DQp0YWJfbHVjcm9fZGlyZXRvciA8LSBpbWRiICU+JQ0KICBncm91cF9ieShkaXJldG9yKSAlPiUgDQogIHN1bW1hcmlzZShsdWNyb19tZWRpbyA9IG1lYW4ocmVjZWl0YSAtIG9yY2FtZW50bywgbmEucm0gPSBUUlVFKSkNCnRhYl9sdWNyb19kaXJldG9yDQpgYGANCg0KRSBzZSBxdWlzZXJtb3MgY29sb2NhciBlc3NhIGluZm9ybWHDp8OjbyBuYSBiYXNlIG9yaWdpbmFsPyBCYXN0YSB1c2FyIGEgZnVuw6fDo28gYGxlZnRfam9pbigpYCB1dGlsaXphbmRvIGEgY29sdW5hIGBkaXJldG9yYCBjb21vIGNoYXZlLiBPYnNlcnZlIHF1ZSBhIGNvbHVuYSBgbHVjcm9fbWVkaW9gIGFwYXJlY2UgYWdvcmEgbm8gZmltIGRhIHRhYmVsYS4NCg0KYGBge3J9DQppbWRiX2NvbV9sdWNyb19tZWRpbyA8LSBsZWZ0X2pvaW4oaW1kYiwgdGFiX2x1Y3JvX2RpcmV0b3IsIGJ5ID0gImRpcmV0b3IiKQ0KaW1kYl9jb21fbHVjcm9fbWVkaW8NCmBgYA0KDQpOYSB0YWJlbGEgYGltZGJfY29tX2x1Y3JvX21lZGlvYCwgY29tbyBuYSB0YWJlbGEgYGltZGJgLCBjYWRhIGxpbmhhIGNvbnRpbnVhIGEgcmVwcmVzZW50YXIgdW0gZmlsbWUgZGlmZXJlbnRlLCBtYXMgYWdvcmEgdGVtb3MgdGFtYsOpbSBhIGluZm9ybWHDp8OjbyBkbyBsdWNybyBtw6lkaW8gZG8gZGlyZXRvciBkZSBjYWRhIGZpbG1lLiANCg0KQSBwcmltZWlyYSBsaW5oYSwgcG9yIGV4ZW1wbG8sIHRyYXogYXMgaW5mb3JtYcOnw7VlcyBkbyBmaWxtZSBBdmF0YXIuIE8gdmFsb3IgZG8gYGx1Y3JvX21lZGlvYCBuZXNzYSBsaW5oYSByZXByZXNlbnRhIG8gbHVjcm8gbcOpZGlvIGRlIHRvZG9zIG9zIGZpbG1lcyBkbyBKYW1lcyBDYW1lcm9uLCBxdWUgw6kgbyBkaXJldG9yIGRlIEF2YXRhci4gQ29tIGVzc2EgaW5mb3JtYcOnw6NvLCBwb2RlbW9zIGNhbGN1bGFyIG8gcXVhbnRvIG8gbHVjcm8gZG8gQXZhdGFyIHNlIGFmYXN0YSBkbyBsdWNybyBtw6lkaW8gZG8gSmFtZXMgQ2FtZXJvbi4NCg0KYGBge3J9DQppbWRiX2NvbV9sdWNyb19tZWRpbyAlPiUgDQogIG11dGF0ZSgNCiAgICBsdWNybyA9IHJlY2VpdGEgLSBvcmNhbWVudG8sDQogICAgbHVjcm9fcmVsYXRpdm8gPSAobHVjcm8gLSBsdWNyb19tZWRpbykvbHVjcm9fbWVkaW8sDQogICAgbHVjcm9fcmVsYXRpdm8gPSBzY2FsZXM6OnBlcmNlbnQobHVjcm9fcmVsYXRpdm8pDQogICkgJT4lIA0KICBzZWxlY3QodGl0dWxvLCBkaXJldG9yLCBsdWNybywgbHVjcm9fbWVkaW8sIGx1Y3JvX3JlbGF0aXZvKQ0KYGBgDQoNCk9ic2VydmFtb3MgZW50w6NvIHF1ZSBvIEF2YXRhciBvYnRldmUgdW0gbHVjcm8gYXByb3hpbWFkYW1lbnRlIDE2OSUgbWFpb3IgcXVlIGEgbcOpZGlhIGRvcyBmaWxtZXMgZG8gSmFtZXMgQ2FtZXJvbi4NCg0KQWzDqW0gZGEgZnVuw6fDo28gYGxlZnRfam9pbigpYCwgdGFtYsOpbSBzw6NvIG11aXRvIHV0aWxpemFkYXMgYXMgZnVuw6fDtWVzIGByaWdodF9qb2luKClgIGUgYGZ1bGxfam9pbigpYC4NCg0KLSBgcmlnaHRfam9pbigpYDogcmV0b3JuYSB0b2RhcyBhcyBsaW5oYXMgZGEgYmFzZSBgeWAgZSB0b2RhcyBhcyBjb2x1bmFzIGRhcyBiYXNlcyBgeGAgZSBgeWAuIExpbmhhcyBkZSBgeWAgc2VtIGNvcnJlc3BvbmRlbnRlcyBlbSBgeGAgcmVjZWJlcsOjbyBgTkFgIG5hIG5vdmEgYmFzZS4NCg0KLSBgZnVsbF9qb2luKClgOiByZXRvcm5hIHRvZGFzIGFzIGxpbmhhcyBlIGNvbHVuYXMgZGUgYHhgZSBgeWAuIFZhbG9yZXMgc2VtIGNvcnJlc3BvbmTDqm5jaWEgZW50cmUgYXMgYmFzZXMgcmVjZWJlcsOjbyBgTkFgIG5hIG5vdmEgYmFzZS4NCg0KQSBmaWd1cmEgYSBzZWd1aXIgZXNxdWVtYXRpemEgYXMgb3BlcmHDp8O1ZXMgZGVzc2FzIGZ1bsOnw7VlczoNCg0KYGBge3IgZHBseXItam9pbnMsIGVjaG89RkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJ30NCiMga25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoJ2Fzc2V0cy9pbWcvbWFuaXB1bGFjYW8vam9pbnMucG5nJykNCmBgYA0KDQojIyMjIEV4ZXJjw61jaW9zIHstfQ0KDQoqKjEuKiogVXRpbGl6ZSBhIGJhc2UgYGltZGJgIHBhcmEgcmVzb2x2ZXIgb3MgaXRlbnMgYSBzZWd1aXIuDQoNCioqYS4qKiBTYWx2ZSBlbSB1bSBub3ZvIG9iamV0byB1bWEgdGFiZWxhIGNvbSBhDQpub3RhIG3DqWRpYSBkb3MgZmlsbWVzIGRlIGNhZGEgZGlyZXRvci4gRXNzYSB0YWJlbGENCmRldmUgY29udGVyIGR1YXMgY29sdW5hcyAoYGRpcmV0b3JgIGUgYG5vdGFfaW1kYl9tZWRpYWApDQplIGNhZGEgbGluaGEgZGV2ZSBzZXIgdW0gZGlyZXRvciBkaWZlcmVudGUuDQoNCioqYi4qKiBVc2UgbyBgbGVmdF9qb2luKClgIHBhcmEgdHJhemVyIGEgY29sdW5hDQpgbm90YV9pbWRiX21lZGlhYCBkYSB0YWJlbGEgZG8gaXRlbSBhbnRlcmlvcg0KcGFyYSBhIHRhYmVsYSBgaW1kYmAgb3JpZ2luYWwuDQoNCg0KIyMjIGRwbHlyIDEuMA0KDQpBIHZlcnPDo28gMS4wIGRvIHBhY290ZSBgZHBseXJgIGZvaSBvZmljaWFsbWVudGUgbGFuw6dhZGEgZW0ganVuaG8gZGUgMjAyMCBlIGNvbnRvdSBjb20gZGl2ZXJzYXMgbm92aWRhZGVzLiBWYW1vcyBmYWxhciBkYXMgcHJpbmNpcGFpcyBtdWRhbsOnYXM6DQoNCi0gQSBub3ZhIGZ1bsOnw6NvIGBhY3Jvc3MoKWAsIHF1ZSBmYWNpbGl0YSBhcGxpY2FyIHVtYSBtZXNtYSBvcGVyYcOnw6NvIGVtIHbDoXJpYXMgY29sdW5hcy4NCg0KLSBBIHJlcGFnaW5hZGEgZnVuw6fDo28gYHJvd3dpc2UoKWAsIHBhcmEgZmF6ZXIgb3BlcmHDp8O1ZXMgcG9yIGxpbmhhLg0KDQotIE5vdmFzIGZ1bmNpb25hbGlkYWRlcyBkYXMgZnVuw6fDtWVzIGBzZWxlY3QoKWAgZSBgcmVuYW1lKClgIGUgYSBub3ZhIGZ1bsOnw6NvIGByZWxvY2F0ZSgpYC4NCg0KUGFyYSB0cmFiYWxoYXIgZXNzYXMgZnVuw6fDtWVzLCB2YW1vcyB1dGlsaXphciBhIGJhc2UgYGNhc2FzYCBkbyBwYWNvdGUgYGRhZG9zYC4gUGFyYSBpbnN0YWxhciBlc3NlIHBhY290ZSwgcm9kZSBvcyBjw7NkaWdvcyBhYmFpeG86DQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQojIGluc3RhbGwucGFja2FnZXMoInJlbW90ZXMiKQ0KIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiY2llbmNpYWRlZGF0b3MvZGFkb3MiKQ0KYGBgDQoNClBhcmEgdHJhemVyIG9zIGRhZG9zIHBhcmEgbyBub3NzbyAqZW52aXJvbm1lbnQqLCBwb2RlbW9zIHJvZGFyOg0KDQpgYGB7cn0NCmNhc2FzIDwtIGRhZG9zOjpjYXNhcw0KYGBgDQoNCkEgYmFzZSBgY2FzYXNgIHBvc3N1aSBkYWRvcyBkZSB2ZW5kYSBkZSBjYXNhcyBuYSBjaWRhZGUgZGUgQW1lcywgbm9zIEVzdGFkb3MgVW5pZG9zLiBTw6NvICAyOTMwIGxpbmhhcyBlIDc3IGNvbHVuYXMsIHNlbmRvIHF1ZSBjYWRhIGxpbmhhIGNvcnJlc3BvbmRlIGEgdW1hIGNhc2EgdmVuZGlkYSBlIGNhZGEgY29sdW5hIGEgdW1hIGNhcmFjdGVyw61zdGljYSBkYSBjYXNhIG91IGRhIHZlbmRhLiBFc3NhIHZlcnPDo28gw6kgdW1hIHRyYWR1w6fDo28gZGEgYmFzZSBvcmlnaW5hbCwgcXVlIHBvZGUgc2VyIGVuY29udHJhZGEgbm8gcGFjb3RlIGBBbWVzSG91c2luZ2A6DQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQppbnN0YWxsLnBhY2thZ2VzKCJBbWVzSG91c2luZyIpDQpkYXRhKGFtZXNfcmF3LCBwYWNrYWdlID0gIkFtZXNIb3VzaW5nIikNCmBgYA0KDQojIyMjIEEgZnVuw6fDo28gYGFjcm9zcygpYCB7LX0NCg0KQSBmdW7Dp8OjbyBgYWNyb3NzKClgIHN1YnN0aXR1aSBhIGZhbcOtbGlhIGRlIHZlcmJvcyBgX2FsbCgpYCwgYF9pZmAgZSBgX2F0KClgLiBBIGlkZWlhIMOpIGZhY2lsaXRhciBhIGFwbGljYcOnw6NvIGRlIHVtYSBvcGVyYcOnw6NvIGEgZGl2ZXJzYXMgY29sdW5hcyBkYSBiYXNlLiBQYXJhIHN1bWFyaXphciBhIGJhc2UgcGFyYSBtYWlzIGRlIHVtYSB2YXJpw6F2ZWwsIGFudGlnYW1lbnRlIHBvZGVyw61hbW9zIGZhemVyOg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBncm91cF9ieShnZXJhbF9xdWFsaWRhZGUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgbG90ZV9hcmVhX21lZGlhID0gbWVhbihsb3RlX2FyZWEsIG5hLnJtID0gVFJVRSksDQogICAgdmVuZGFfdmFsb3JfbWVkaW8gPSBtZWFuKHZlbmRhX3ZhbG9yLCBuYS5ybSA9IFRSVUUpDQogICkNCmBgYA0KDQpPdSBlbnTDo28gdXRpbGl6YXIgYSBmdW7Dp8OjbyBgc3VtbWFyaXNlX2F0KClgOg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBncm91cF9ieShnZXJhbF9xdWFsaWRhZGUpICU+JQ0KICBzdW1tYXJpc2VfYXQoDQogICAgLnZhcnMgPSB2YXJzKGxvdGVfYXJlYSwgdmVuZGFfdmFsb3IpLA0KICAgIGxpc3QobWVhbiksDQogICAgbmEucm0gPSBUUlVFDQogICkNCmBgYA0KDQpDb20gYSBub3ZhIGZ1bsOnw6NvIGBhY3Jvc3MoKWAsIGZhemVtb3M6DQoNCmBgYHtyfQ0KY2FzYXMgJT4lDQogIGdyb3VwX2J5KGdlcmFsX3F1YWxpZGFkZSkgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoDQogICAgLmNvbHMgPSBjKGxvdGVfYXJlYSwgdmVuZGFfdmFsb3IpLA0KICAgIC5mbnMgPSBtZWFuLCANCiAgICBuYS5ybSA9IFRSVUUNCiAgKSkNCmBgYA0KDQpBIHNpbnRheGUgw6kgcGFyZWNpZGEgY29tIGEgZnVuw6fDo28gYHN1bW1hcmlzZV9hdCgpYCwgbWFzIGFnb3JhIG7Do28gcHJlY2lzYW1vcyBtYWlzIHVzYXIgYSBmdW7Dp8OjbyBgdmFycygpYCBlIG5lbSB1c2FyIGBsaXN0KG5vbWVfZGFfZnVuY2FvKWBwYXJhIGRlZmluaXIgYSBmdW7Dp8OjbyBhcGxpY2FkYSBuYXMgY29sdW5hcy4NCg0KVXNhbmRvIGBhY3Jvc3MoKWAsIHBvZGVtb3MgZmFjaWxtZW50ZSBhcGxpY2FyIHVtYSBmdW7Dp8OjbyBlbSB0b2RhcyBhcyBjb2x1bmFzIGRhIG5vc3NhIGJhc2UuIEFiYWl4bywgY2FsY3VsYW1vcyBvIG7Dum1lcm8gZGUgdmFsb3JlcyBkaXN0aW50b3MgcGFyYSB0b2RhcyBhcyB2YXJpw6F2ZWlzIGRhIGJhc2UgYGNhc2FzYC4gTyBwYWRyw6NvIGRvIHBhcsOibWV0cm8gYC5jb2xzYCDDqSBgZXZlcml0aGluZygpYCwgcXVlIHJlcHJlc2VudGEgInRvZGFzIGFzIGNvbHVuYXMiLg0KDQpgYGB7cn0NCmNhc2FzICU+JSANCiAgc3VtbWFyaXNlKGFjcm9zcyguZm5zID0gbl9kaXN0aW5jdCkpDQpgYGANCg0KUGFyYSBmYXplciBlc3NhIG1lc21hIG9wZXJhw6fDo28sIGFudGVzIHV0aWxpemFyw61hbW9zIGEgZnVuw6fDo28gYHN1bW1hcmlzZV9hbGwoKWAuDQoNCmBgYHtyfQ0KY2FzYXMgJT4lIA0KICBzdW1tYXJpc2VfYWxsKC5mdW5zID0gfm5fZGlzdGluY3QoLngpKQ0KYGBgDQoNClNlIHF1aXNlcm1vcyBzZWxlY2lvbmFyIGFzIGNvbHVuYXMgYSBzZXJlbSBtb2RpZmljYWRhcyBhIHBhcnRpciBkZSB1bSB0ZXN0ZSBsw7NnaWNvLCB1dGlsaXphbW9zIG8gYWp1ZGFudGUgYHdoZXJlKClgLg0KDQpObyBleGVtcGxvIGFiYWl4bywgY2FsY3VsYW1vcyBvIG7Dum1lcm8gZGUgdmFsb3JlcyBkaXN0aW50b3MgZGFzIGNvbHVuYXMgZGUgY2F0ZWfDs3JpY2FzLg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKHdoZXJlKGlzLmNoYXJhY3RlciksIG5fZGlzdGluY3QpKQ0KYGBgDQoNClRvZGFzIGFzIGNvbHVuYXMgZGEgYmFzZSByZXN1bHRhbnRlIGVyYW0gY29sdW5hcyBjb20gY2xhc3NlIGBjaGFyYWN0ZXJgIG5hIGJhc2UgYGNhc2FzYC4NCg0KQW50ZXJpb3JtZW50ZSwgdXRpbGl6w6F2YW1vcyBhIGZ1bsOnw6NvIGBzdW1tYXJpc2VfaWYoKWAuDQoNCmBgYHtyfQ0KY2FzYXMgJT4lDQogIHN1bW1hcmlzZV9pZihpcy5jaGFyYWN0ZXIsIG5fZGlzdGluY3QpDQpgYGANCg0KQ29tIG8gYGFjcm9zcygpYCwgcG9kZW1vcyBjb21iaW5hciBvcyBlZmVpdG9zIGRlIHVtIGBzdW1tYXJpc2VfaWYoKWAgZSB1bSBgc3VtbWFyaXNlX2F0KClgIGVtIHVtIMO6bmljbyBgc3VtbWFyaXNlKClgLiBBIHNlZ3VpciwgY2FsY3VsYW1vcyBhcyDDoXJlYXMgbcOpZGlhcywgZ2FyYW50aW5kbyBxdWUgcGVnYW1vcyBhcGVuYXMgdmFyacOhdmVpcyBudW3DqXJpY2FzLg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpICYgY29udGFpbnMoIl9hcmVhIiksIG1lYW4sIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KQWzDqW0gZGlzc28sIGNvbSBhIGZ1bsOnw6NvIGBhY3Jvc3MoKWAsIHBvZGVtb3MgZmF6ZXIgc3VtYXJpemHDp8O1ZXMgY29tcGxleGFzIHF1ZSBuw6NvIHNlcmlhIHBvc3PDrXZlbCB1dGlsaXphbmRvIGFwZW5hcyBhcyBmdW7Dp8O1ZXMgYHN1bW1hcmlzZSgpYCwgYHN1bW1hcmlzZV9pZigpYCBlIGBzdW1tYXJpc2VfYXQoKWAuIE5vIGV4ZW1wbG8gYSBzZWd1aXIsIGNhbGN1bGFtb3MgYSBtw6lkaWEgZGFzIMOhcmVhcywgbyBuw7ptZXJvIGRlIGBOQXNgIGRlIHZhcmnDoXZlaXMgY2F0ZWfDs3JpY2FzIGUgbyBuw7ptZXJvIGRlIGNhc2FzIHBhcmEgY2FkYSB0aXBvIGRlIGZ1bmRhw6fDo28uIFR1ZG8gZW0gdW0gbWVzbW8gYHN1bW1hcmlzZSgpYCENCg0KYGBge3J9DQpjYXNhcyAlPiUNCiAgZ3JvdXBfYnkoZnVuZGFjYW9fdGlwbykgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYykgJiBjb250YWlucygiYXJlYSIpLCBtZWFuLCBuYS5ybSA9IFRSVUUpLA0KICAgIGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCB+c3VtKGlzLm5hKC54KSkpLA0KICAgIG5fb2JzID0gbigpLA0KICApICU+JSANCiAgc2VsZWN0KDE6NCwgbl9vYnMpDQpgYGANCg0KRW1ib3JhIGEgbm92YSBzaW50YXhlLCB1c2FuZG8gYGFjcm9zcygpYCwgbsOjbyBzZWphIG11aXRvIGRpZmVyZW50ZSBkbyBxdWUgZmF6w61hbW9zIGFudGVzLCByZWFsaXphciBzdW1hcml6YcOnw7VlcyBjb21wbGV4YXMgbsOjbyDDqSBhIMO6bmljYSB2YW50YWdlbSBkZXNzZSBub3ZvICpmcmFtZXdvcmsqLg0KDQpPIGBhY3Jvc3MoKWAgcG9kZSBzZXIgdXRpbGl6YWRvIGVtIHRvZG9zIG9zIHZlcmJvcyBkbyBge2RwbHlyfWAgKGNvbSBleGNlw6fDo28gZG8gYHNlbGVjdCgpYCBlIGByZW5hbWUoKWAsIGrDoSBxdWUgZWxlIG7Do28gdHLDoXMgdmFudGFnZW5zIGNvbSByZWxhw6fDo28gYW8gcXVlIGrDoSBwb2RpYSBzZXIgZmVpdG8pIGUgaXNzbyB1bmlmaWNhIG8gbW9kbyBkZSBmYXplcm1vcyBlc3NhcyBvcGVyYcOnw7VlcyBubyBgZHBseXJgLiBFbSB2ZXogZGUgdGVybW9zIHVtYSBmYW3DrWxpYSBkZSBmdW7Dp8O1ZXMgcGFyYSBjYWRhIHZlcmJvLCB0ZW1vcyBhZ29yYSBhcGVuYXMgbyBwcsOzcHJpbyB2ZXJibyBlIGEgZnVuw6fDo28gYGFjcm9zcygpYC4NCg0KVmFtb3MgdmVyIHVtIGV4ZW1wbG8gcGFyYSBvIGBtdXRhdGUoKWAgZSBwYXJhIG8gYGZpbHRlcigpYC4NCg0KTyBjw7NkaWdvIGFiYWl4byB0cmFuc2Zvcm1hIHRvZGFzIGFzIHZhcmnDoXZlaXMgcXVlIHBvc3N1ZW0gImFyZWEiIG5vIG5vbWUsIHBhc3NhbmRvIG9zIHZhbG9yZXMgZGUgcMOpcyBxdWFkcmFkb3MgcGFyYSBtZXRyb3MgcXVhZHJhZG9zLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIG11dGF0ZShhY3Jvc3MoDQogICAgY29udGFpbnMoImFyZWEiKSwNCiAgICB+IC54IC8gMTAuNzY0DQogICkpDQpgYGANCg0KSsOhIG8gY8OzZGlnbyBhIHNlZ3VpciBmaWx0cmEgYXBlbmFzIGFzIGNhc2FzIHF1ZSBwb3NzdWVtIHZhcmFuZGEgYWJlcnRhLCBjZXJjYSBlIGxhcmVpcmEgKG8gYE5BYCBuZXNzYSBiYXNlIHNpZ25pZmljYSBxdWUgYSBjYXNhIG7Do28gcG9zc3VpIHRhbCBjYXJhY3RlcsOtc3RpY2EpLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIGZpbHRlcihhY3Jvc3MoDQogICAgYyh2YXJhbmRhX2FiZXJ0YV9hcmVhLCBjZXJjYV9xdWFsaWRhZGUsIGxhcmVpcmFfcXVhbGlkYWRlKSwNCiAgICB+IWlzLm5hKC54KQ0KICApKSANCmBgYA0KDQpOw6NvIHByZWNpc2Ftb3MgZG8gYGFjcm9zcygpYCBuYSBob3JhIGRlIHNlbGVjaW9uYXIgY29sdW5hcy4gQSBmdW7Dp8OjbyBgc2VsZWN0KClgIGrDoSB1c2EgbmF0dXJhbG1lbnRlIG8gbWVjYW5pc21vIGRlIHNlbGXDp8OjbyBkZSBjb2x1bmFzIHF1ZSBvIGBhY3Jvc3MoKWAgcHJvcG9yY2lvbmEuDQoNCmBgYHtyfQ0KY2FzYXMgJT4lDQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkNCmBgYA0KDQpPIG1lc21vIHZhbGUgcGFyYSBvIGByZW5hbWUoKWAuIFNlIHF1aXNlcm1vcyByZW5vbWVyIHbDoXJpYXMgY29sdW5hcywgYSBwYXJ0aXIgZGUgdW1hIGZ1bsOnw6NvLCB1dGlsaXphbW9zIG8gYHJlbmFtZV93aXRoKClgLg0KDQpgYGB7cn0NCmNhc2FzICU+JQ0KICByZW5hbWVfd2l0aCh0b3VwcGVyLCBjb250YWlucygidmVuZGEiKSkNCmBgYA0KDQojIyMjIEEgZnVuw6fDo28gYHJlbG9jYXRlKClgIHstfQ0KDQpPIGB7ZHBseXJ9YCBwb3NzdWkgYWdvcmEgdW1hIGZ1bsOnw6NvIHByw7NwcmlhIHBhcmEgcmVvcmdhbml6YXIgY29sdW5hczogYHJlbG9jYXRlKClgLiBQb3IgcGFkcsOjbywgZWxhIGNvbG9jYSB1bWEgb3UgbWFpcyBjb2x1bmFzIG5vIGNvbWXDp28gZGEgYmFzZS4NCg0KYGBge3J9DQpjYXNhcyAlPiUNCiAgcmVsb2NhdGUodmVuZGFfdmFsb3IsIHZlbmRhX3RpcG8pDQpgYGANCg0KUG9kZW1vcyB1c2FyIG9zIGFyZ3VtZW50b3MgYC5hZnRlcmAgZSBgLmJlZm9yZWAgcGFyYSBmYXplciBtdWRhbsOnYXMgbWFpcyBjb21wbGV4YXMuDQoNCk8gY8OzZGlnbyBiYWl4byBjb2xvY2EgYSBjb2x1bmEgYHZlbmRhX2Fub2AgZGVwb2lzIGRhIGNvbHVuYSBgY29uc3RydWNhb19hbm9gLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIHJlbG9jYXRlKHZlbmRhX2FubywgLmFmdGVyID0gY29uc3RydWNhb19hbm8pDQpgYGANCg0KDQpPIGPDs2RpZ28gYmFpeG8gY29sb2NhIGEgY29sdW5hIGB2ZW5kYV9hbm9gIGFudGVzIGRhIGNvbHVuYSBgY29uc3RydWNhb19hbm9gLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KY2FzYXMgJT4lDQogIHJlbG9jYXRlKHZlbmRhX2FubywgLmJlZm9yZSA9IGNvbnN0cnVjYW9fYW5vKQ0KYGBgDQoNCiMjIyMgQSBmdW7Dp8OjbyBgcm93d2lzZSgpYCB7LX0NCg0KUG9yIGZpbSwgdmFtb3MgZGlzY3V0aXIgb3BlcmHDp8O1ZXMgZmVpdGFzIHBvciBsaW5oYS4gVG9tZSBjb21vIGV4ZW1wbG8gYSB0YWJlbGEgYWJhaXhvLiBFbGEgYXByZXNlbnRhIGFzIG5vdGFzIGRlIGFsdW5vcyBlbSBxdWF0cm8gcHJvdmFzLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyA8LSB0aWJibGUoDQogIHN0dWRlbnRfaWQgPSAxOjUsDQogIHByb3ZhMSA9IHNhbXBsZSgwOjEwLCA1KSwNCiAgcHJvdmEyID0gc2FtcGxlKDA6MTAsIDUpLA0KICBwcm92YTMgPSBzYW1wbGUoMDoxMCwgNSksDQogIHByb3ZhNCA9IHNhbXBsZSgwOjEwLCA1KQ0KKQ0KdGFiX25vdGFzDQpgYGANCg0KU2UgcXVpc2VybW9zIGdlcmFyIHVtYSBjb2x1bmEgY29tIGEgbm90YSBtw6lkaWEgZGUgY2FkYSBhbHVubyBuYXMgcXVhdHJvIHByb3ZhcywgbsOjbyBwb2RlcsOtYW1vcyB1c2FyIG8gYG11dGF0ZSgpYCBkaXJldGFtZW50ZS4NCg0KYGBge3J9DQp0YWJfbm90YXMgJT4lIG11dGF0ZShtZWRpYSA9IG1lYW4oYyhwcm92YTEsIHByb3ZhMiwgcHJvdmEzLCBwcm92YTQpKSkNCmBgYA0KDQpOZXN0ZSBjYXNvLCB0b2RhcyBhcyBjb2x1bmFzIGVzdMOjbyBzZW5kbyBlbXBpbGhhZGFzIGUgZ2VyYW5kbyB1bWEgw7puaWNhIG3DqWRpYSwgcGFzc2FkYSBhIHRvZGFzIGFzIGxpbmhhcyBkYSBjb2x1bmEgYG1lZGlhYC4NCg0KUGFyYSBmYXplcm1vcyBhIGNvbnRhIHBhcmEgY2FkYSBhbHVubywgcG9kZW1vcyBhZ3J1cGFyIHBvciBhbHVuby4gQWdvcmEgc2ltIGEgbcOpZGlhIMOpIGNhbGN1bGFkYSBhcGVuYXMgbmFzIG5vdGFzIGRlIGNhZGEgZXN0dWRhbnRlLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyAlPiUNCiAgZ3JvdXBfYnkoc3R1ZGVudF9pZCkgJT4lDQogIG11dGF0ZShtZWRpYSA9IG1lYW4oYyhwcm92YTEsIHByb3ZhMiwgcHJvdmEzLCBwcm92YTQpKSkNCmBgYA0KDQpUYW1iw6ltIHBvZGVtb3Mgbm9zIGFwcm92ZWl0YXIgZGEgc2ludGF4ZSBkbyBgYWNyb3NzKClgIG5lc3RlIGNhc28uIFBhcmEgaXNzbywgcHJlY2lzYW1vcyBzdWJzdHV0aXIgYSBmdW7Dp8OjbyBgYygpYCBwZWxhIGZ1bsOnw6NvIGBjX2Fjcm9zcygpYC4NCg0KYGBge3J9DQp0YWJfbm90YXMgJT4lDQogIGdyb3VwX2J5KHN0dWRlbnRfaWQpICU+JQ0KICBtdXRhdGUobWVkaWEgPSBtZWFuKGNfYWNyb3NzKHN0YXJ0c193aXRoKCJwcm92YSIpKSkpDQpgYGANCg0KRXF1aXZhbGVudGVtZW50ZSBhbyBgZ3JvdXBfYnkoKWAsIG5lc3RlIGNhc28sIHBvZGVtb3MgdXNhciBhIGZ1bsOnw6NvIGByb3d3aXNlKClgLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyAlPiUNCiAgcm93d2lzZShzdHVkZW50X2lkKSAlPiUNCiAgbXV0YXRlKG1lZGlhID0gbWVhbihjX2Fjcm9zcyhzdGFydHNfd2l0aCgicHJvdmEiKSkpKQ0KYGBgDQoNCkVsYSDDqSBtdWl0byDDunRpbCBxdWFuZG8gcXVlcmVtb3MgZmF6ZXIgb3BlcmHDp8OjbyBwb3IgbGluaGFzLCBtYXMgbsOjbyB0ZW1vcyB1bWEgY29sdW5hIGRlIGlkZW50aWZpY2HDp8Ojby4gUG9yIHBhZHLDo28sIHNlIG7Do28gaW5kaWNhcm1vcyBuZW5odW1hIGNvbHVuYSwgY2FkYSBsaW5oYSBzZXLDoSB1bSAiZ3J1cG8iLg0KDQpgYGB7cn0NCnRhYl9ub3RhcyAlPiUNCiAgcm93d2lzZSgpICU+JQ0KICBtdXRhdGUobWVkaWEgPSBtZWFuKGNfYWNyb3NzKHN0YXJ0c193aXRoKCJwcm92YSIpKSkpDQpgYGANCg0KVmVqYSBxdWUgYHN0dWRlbnRfaWRgIG7Do28gw6kgcGFzc2FkYSBwYXJhIGEgZnVuw6fDo28gYHJvd3dpc2UoKWAuIE7Do28gcHJlY2lzYXLDrWFtb3MgZGVzc2EgY29sdW5hIG5hIGJhc2UgcGFyYSByZXByb2R1emlyIGEgZ2VyYcOnw6NvIGRhIGNvbHVtYSBgbWVkaWFgIG5lc3RlIGNhc28uDQoNCiMjIyMgRXhlcmPDrWNpb3Mgey19DQoNCkEgYmFzZSBgY2FzYXNgIGFiYWl4byBwb2RlIHNlciBlbmNvbnRyYWRhIGEgcGFydGlyIGRvIGPDs2RpZ28gYWJhaXhvOg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImNpZW5jaWFkZWRhdG9zL2RhZG9zIikNCmxpYnJhcnkoZGFkb3MpDQpkYWRvczo6Y2FzYXMNCmBgYA0KDQoNCioqMS4qKiBSZWVzY3JldmEgb3MgY8OzZGlnb3MgYWJhaXhvIHV0aWxpemFuZG8gYXMgZnVuw6fDtWVzIGBhY3Jvc3MoKWAgZSBgd2hlcmUoKWAuDQoNCioqYS4qKiANCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmNhc2FzICU+JQ0KICBncm91cF9ieShnZXJhbF9xdWFsaWRhZGUpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgYWNpbWFfc29sb19hcmVhX21lZGlhID0gbWVhbihhY2ltYV9zb2xvX2FyZWEsIG5hLnJtID0gVFJVRSksDQogICAgZ2FyYWdlbV9hcmVhX21lZGlhID0gbWVhbihnYXJhZ2VtX2FyZWEsIG5hLnJtID0gVFJVRSksDQogICAgdmFsb3JfdmVuZGFfbWVkaW8gPSBtZWFuKHZlbmRhX3ZhbG9yLCBuYS5ybSA9IFRSVUUpDQogICkNCmBgYA0KDQoqKmIuKioNCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmNhc2FzICU+JQ0KICBmaWx0ZXJfYXQoDQogICAgdmFycyhwb3Jhb19xdWFsaWRhZGUsIHZhcmFuZGFfZmVjaGFkYV9hcmVhLCBjZXJjYV9xdWFsaWRhZGUpLA0KICAgIH4haXMubmEoLngpDQogICkNCmBgYA0KDQoqKmMuKioNCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmNhc2FzICU+JQ0KICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCB+dGlkeXI6OnJlcGxhY2VfbmEoLngsIHJlcGxhY2UgPSAiTsOjbyBwb3NzdWkiKSkNCmBgYA0KDQoqKjIuKiogVXRpbGl6YW5kbyBhIGJhc2UgYGNhc2FzYCwgcmVzb2x2YSBvcyBpdGVucyBhIHNlZ3Vpci4NCg0KLSAqKmEuKiogVXNhbmRvIG8gYGNhc2Vfd2hlbigpYCBjcmllIHVtIGPDs2RpZ28gcGFyYSBjYXRlZ29yaXphciBhIHZhcmnDoXZlbCB2ZW5kYV92YWxvciBkYSBzZWd1aW50ZSBtYW5laXJhOg0KDQogIC0gKipiYXJhdGEqKjogXCQwIGEgXCQxMjkuNTAwICANCiAgLSAqKnByZcOnbyBtZWRpYW5vKio6IFwkMTI5LjUwMCBhIFwkMTgwLjc5Ng0KICAtICoqY2FyYSoqOiBcJCAxODAuNzk2IGEgXCQyMTMuNTAwDQogIC0gKiptdWl0byBjYXJhKio6IG1haW9yIHF1ZSBcJDIxMy41MDANCg0KPGRpdiBzdHlsZSA9ICJoZWlnaHQ6IDEwcHg7Ij48L2Rpdj4NCg0KLSAqKmIuKiogVXRpbGl6ZSBvIGPDs2RpZ28gZmVpdG8gbmEgbGV0cmEgKGEpIHBhcmEgYWdydXBhciBhIGJhc2UgYGNhc2FzYCBwZWxhIHZhcmnDoXZlbCB2ZW5kYV92YWxvciBjYXRlZ29yaXphZGEgZSBjYWxjdWxhciB0b2RhcyBhcyDDoXJlYXMgbcOpZGlhcyBwYXJhIGNhZGEgdW1hIGRlc3NhcyBjYXRlZ29yaWFzLg0KDQoqKjMuKiogRXNjcmV2YSB1bSBjw7NkaWdvIHF1ZSByZWNlYmEgYSBiYXNlIGBjYXNhc2AgZSByZXRvcm5lIHVtYSB0YWJlbGEgY29tIGFwZW5hcw0KDQotICoqYS4qKiBhcyBjb2x1bmFzIHJlZmVyZW50ZXMgw6AgZ2FyYWdlbSBkYSBjYXNhLg0KDQotICoqYi4qKiBhcyBjb2x1bmFzIHJlZmVyZW50ZXMgYSB2YXJpw6F2ZWlzIGRlIHF1YWxpZGFkZS4NCg0KLSAqKmMuKiogY29sdW5hcyBudW3DqXJpY2FzIHF1ZSByZXByZXNlbnRhbSDDoXJlYXMgZGEgY2FzYSBlIGRvIHRlcnJlbm8uDQoNCi0gKipkLioqIGNvbHVuYXMgbnVtw6lyaWNhcy4NCg0KLSAqKmUuKiogY29sdW5hcyByZWZlcmVudGVzIMOgIHBpc2NpbmEsIHBvcsOjbyBlIG8gdmFsb3IgZGUgdmVuZGEuDQoNCioqNC4qKiBVc2FuZG8gYSBmdW7Dp8OjbyBgcmVuYW1lX3dpdGgoKWAsIHRyb3F1ZSB0b2RvcyBvcyBgIl8iYCBkb3Mgbm9tZXMgZGFzIGNvbHVuYXMgcG9yIHVtIGVzcGHDp28gYCIgImAuDQoNCioqNS4qKiBFc2NyZXZhIHVtIGPDs2RpZ28gcGFyYSBjb2xvY2FyIHRvZGFzIGFzIGNvbHVuYXMgcmVsYXRpdmFzIGEgdmVuZGEgbm8gY29tZcOnbyBkYSBiYXNlIGBjYXNhc2AuDQoNCioqNi4qKiA1LiBFc2NyZXZhIHVtIGPDs2RpZ28gcGFyYSBjb2xvY2FyIHRvZGFzIGFzIGNvbHVuYXMgbnVtw6lyaWNhcyBkYSBiYXNlIGBjYXNhc2Agbm8gY29tZcOnbyBkYSB0YWJlbGEgZSB0b2RhcyBhcyBjb2x1bmFzIGNhdGVnw7NyaWNhcyBubyBmaW5hbC4NCg==